Completed
Push — 16.1 ( 74433c...7a42e7 )
by Nathan
14:21
created

app.classes.calendar.View.extend.granularity   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
/**
2
 * EGroupware - Calendar - Javascript UI
3
 *
4
 * @link http://www.egroupware.org
5
 * @package calendar
6
 * @author Hadi Nategh	<hn-AT-stylite.de>
7
 * @author Nathan Gray
8
 * @copyright (c) 2008-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
/*egw:uses
14
	/etemplate/js/etemplate2.js;
15
	/calendar/js/et2_widget_owner.js;
16
	/calendar/js/et2_widget_timegrid.js;
17
	/calendar/js/et2_widget_planner.js;
18
	/vendor/bower-asset/jquery-touchswipe/jquery.touchSwipe.js;
19
*/
20
21
/**
22
 * UI for calendar
23
 *
24
 * Calendar has multiple different views of the same data.  All the templates
25
 * for the different view are loaded at the start, then the view objects
26
 * in app.classes.calendar.views are used to manage the different views.
27
 * update_state() is used to change the state between the different views, as
28
 * well as adjust only a single part of the state while keeping the rest unchanged.
29
 *
30
 * The event widgets (and the nextmatch) get the data from egw.data, and they
31
 * register update callbacks to automatically update when the data changes.  This
32
 * means that when we update something on the server, to update the UI we just need
33
 * to send back the new data and if the event widget still exists it will update
34
 * itself.  See calendar_uiforms->ajax_status().
35
 *
36
 * To reduce server calls, we also keep a map of day => event IDs.  This allows
37
 * us to quickly change views (week to day, for example) without requesting additional
38
 * data from the server.  We keep that map as long as only the date (and a few
39
 * others - see update_state()) changes.  If user or any of the other filters are
40
 * changed, we discard the daywise cache and ask the server for the filtered events.
41
 *
42
 * @augments AppJS
43
 */
44
app.classes.calendar = (function(){ "use strict"; return AppJS.extend(
45
{
46
	/**
47
	 * application name
48
	 */
49
	appname: 'calendar',
50
51
	/**
52
	 * etemplate for the sidebox filters
53
	 */
54
	sidebox_et2: null,
55
56
	/**
57
	 * Current internal state
58
	 *
59
	 * If you need to change state, you can pass just the fields to change to
60
	 * update_state().
61
	 */
62
	state: {
63
		date: new Date(),
64
		view: egw.preference('saved_states','calendar') ? egw.preference('saved_states','calendar').view : egw.preference('defaultcalendar','calendar') || 'day',
65
		owner: egw.user('account_id')
66
	},
67
68
	/**
69
	 * These are the keys we keep to set & remember the status, others are discarded
70
	 */
71
	states_to_save: ['owner','status_filter','filter','cat_id','view','sortby','planner_view','weekend'],
72
73
	// If you are in one of these views and select a date in the sidebox, the view
74
	// will change as needed to show the date.  Other views will only change the
75
	// date in the current view.
76
	sidebox_changes_views: ['day','week','month'],
77
78
	// Calendar allows other apps to hook into the sidebox.  We keep these etemplates
79
	// up to date as state is changed.
80
	sidebox_hooked_templates: [],
81
82
	// List of queries in progress, to prevent home from requesting the same thing
83
	_queries_in_progress: [],
84
85
	// Calendar-wide autorefresh
86
	_autorefresh_timer: null,
87
88
	/**
89
	 * Constructor
90
	 *
91
	 * @memberOf app.calendar
92
	 */
93
	init: function()
94
	{
95
		// categories have nothing to do with calendar, but eT2 objects loads calendars app.js
96
		if (window.framework && framework.applications.calendar.browser &&
97
			framework.applications.calendar.browser.currentLocation.match('menuaction=preferences\.preferences_categories_ui\.index'))
98
		{
99
			this._super.apply(this, arguments);
100
			return;
101
		}
102
		else// make calendar object available, even if not running in top window, as sidebox does
103
		if (window.top !== window && !egw(window).is_popup() && window.top.app.calendar)
104
		{
105
			window.app.calendar = window.top.app.calendar;
106
			return;
107
		}
108
		else if (window.top == window && !egw(window).is_popup())
109
		{
110
			// Show loading div
111
			egw.loading_prompt(
112
				this.appname,true,egw.lang('please wait...'),
113
				typeof framework !== 'undefined' ? framework.applications.calendar.tab.contentDiv : false,
114
				egwIsMobile()?'horizental':'spinner'
115
			);
116
		}
117
118
		// call parent
119
		this._super.apply(this, arguments);
120
121
		// Scroll
122
		jQuery(jQuery.proxy(this._scroll,this));
123
		jQuery.extend(this.state, this.egw.preference('saved_states','calendar'));
124
	},
125
126
	/**
127
	 * Destructor
128
	 */
129
	destroy: function()
130
	{
131
		// call parent
132
		this._super.apply(this, arguments);
133
134
		// remove top window reference
135
		if (window.top !== window && window.top.app.calendar === this)
136
		{
137
			delete window.top.app.calendar;
138
		}
139
		jQuery('body').off('.calendar');
140
141
		if(this.sidebox_et2)
142
		{
143
			var date = this.sidebox_et2.getWidgetById('date');
144
			jQuery(window).off('resize.calendar'+date.dom_id);
145
		}
146
		this.sidebox_hooked_templates = null;
147
148
		egw_unregisterGlobalShortcut(jQuery.ui.keyCode.PAGE_UP, false, false, false);
149
		egw_unregisterGlobalShortcut(jQuery.ui.keyCode.PAGE_DOWN, false, false, false);
150
151
		// Stop autorefresh
152
		if(this._autorefresh_timer)
153
		{
154
			window.clearInterval(this._autorefresh_timer);
155
			this._autorefresh_timer = null;
156
		}
157
	},
158
159
	/**
160
	 * This function is called when the etemplate2 object is loaded
161
	 * and ready.  If you must store a reference to the et2 object,
162
	 * make sure to clean it up in destroy().
163
	 *
164
	 * @param {etemplate2} _et2 newly ready et2 object
165
	 * @param {string} _name name of template
166
	 */
167
	et2_ready: function(_et2, _name)
168
	{
169
		// call parent
170
		this._super.apply(this, arguments);
171
172
		// Avoid many problems with home
173
		if(_et2.app !== 'calendar' || _name == 'admin.categories.index')
174
		{
175
			egw.loading_prompt(this.appname,false);
176
			return;
177
		}
178
179
		// Re-init sidebox, since it was probably initialized too soon
180
		var sidebox = jQuery('#favorite_sidebox_'+this.appname);
181
		if(sidebox.length == 0 && egw_getFramework() != null)
182
		{
183
			var egw_fw = egw_getFramework();
184
			sidebox= jQuery('#favorite_sidebox_'+this.appname,egw_fw.sidemenuDiv);
185
		}
186
187
		var content = this.et2.getArrayMgr('content');
188
189
		switch (_name)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
190
		{
191
			case 'calendar.sidebox':
192
				this.sidebox_et2 = _et2.widgetContainer;
193
				this.sidebox_hooked_templates.push(this.sidebox_et2);
194
				jQuery(_et2.DOMContainer).hide();
195
196
				// Set client side holiday cache for this year
197
				if(egw.window.et2_calendar_view)
198
				{
199
					egw.window.et2_calendar_view.holiday_cache[content.data.year] = content.data.holidays;
200
					delete content.data.holidays;
201
					delete content.data.year;
202
				}
203
204
				this._setup_sidebox_filters();
205
206
				this.state = content.data;
207
				break;
208
209
			case 'calendar.edit':
210
				if (typeof content.data['conflicts'] == 'undefined')
211
				{
212
					//Check if it's fallback from conflict window or it's from edit window
213
					if (content.data['button_was'] != 'freetime')
214
					{
215
						this.set_enddate_visibility();
216
						this.check_recur_type();
217
						this.edit_start_change();
218
						this.et2.getWidgetById('recur_exception').set_disabled(!content.data.recur_exception ||
219
							typeof content.data.recur_exception[0] == 'undefined');
220
					}
221
					else
222
					{
223
						this.freetime_search();
224
					}
225
					//send Syncronus ajax request to the server to unlock the on close entry
226
					//set onbeforeunload with json request to send request when the window gets close by X button
227
					if (content.data.lock_token)
228
					{
229
						window.onbeforeunload = function () {
230
							this.egw.json('calendar.calendar_uiforms.ajax_unlock',
231
							[content.data.id, content.data.lock_token],null,true,null,null).sendRequest(true);
232
						};
233
					}
234
				}
235
				this.alarm_custom_date();
236
237
				// If title is pre-filled for a new (no ID) event, highlight it
238
				if(content.data && !content.data.id && content.data.title)
239
				{
240
					this.et2.getWidgetById('title').input.select();
241
				}
242
243
				// Disable loading prompt (if loaded nopopup)
244
				egw.loading_prompt(this.appname,false);
245
				break;
246
247
			case 'calendar.freetimesearch':
248
				this.set_enddate_visibility();
249
				break;
250
			case 'calendar.list':
251
				// Wait until _et2_view_init is done
252
				window.setTimeout(jQuery.proxy(function() {
253
					this.filter_change();
254
				},this),0);
255
				break;
256
			case 'calendar.category_report':
257
				this.category_report_init();
258
				break;
259
		}
260
261
		// Record the templates for the views so we can switch between them
262
		this._et2_view_init(_et2,_name);
263
	},
264
265
	/**
266
	 * Observer method receives update notifications from all applications
267
	 *
268
	 * App is responsible for only reacting to "messages" it is interested in!
269
	 *
270
	 * Calendar binds listeners to the data cache, so if the data is updated, the widget
271
	 * will automatically update itself.
272
	 *
273
	 * @param {string} _msg message (already translated) to show, eg. 'Entry deleted'
274
	 * @param {string} _app application name
275
	 * @param {(string|number)} _id id of entry to refresh or null
276
	 * @param {string} _type either 'update', 'edit', 'delete', 'add' or null
277
	 * - update: request just modified data from given rows.  Sorting is not considered,
278
	 *		so if the sort field is changed, the row will not be moved.
279
	 * - edit: rows changed, but sorting may be affected.  Requires full reload.
280
	 * - delete: just delete the given rows clientside (no server interaction neccessary)
281
	 * - add: requires full reload for proper sorting
282
	 * @param {string} _msg_type 'error', 'warning' or 'success' (default)
283
	 * @param {object|null} _links app => array of ids of linked entries
284
	 * or null, if not triggered on server-side, which adds that info
285
	 * @return {false|*} false to stop regular refresh, thought all observers are run
286
	 */
287
	observer: function(_msg, _app, _id, _type, _msg_type, _links)
288
	{
289
		var do_refresh = false;
290
		if(this.state.view === 'listview')
291
		{
292
			app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm').refresh(_id,_type);
293
		}
294
		switch(_app)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
295
		{
296
			case 'infolog':
297
				jQuery('.calendar_calDayTodos')
298
					.find('a')
299
					.each(function(i,a){
300
						var match = a.href.split(/&info_id=/);
301
						if (match && typeof match[1] !="undefined")
302
						{
303
							if (match[1]== _id)	do_refresh = true;
304
						}
305
					});
306
307
				// Unfortunately we do not know what type this infolog is here,
308
				// but we can tell if it's turned off entirely
309
				if(egw.preference('calendar_integration','infolog') !== '0')
310
				{
311
					if (jQuery('div [data-app="infolog"][data-app_id="'+_id+'"]').length > 0) do_refresh = true;
312
					switch (_type)
313
					{
314
						case 'add':
315
							do_refresh = true;
316
							break;
317
					}
318
				}
319
				if (do_refresh)
320
				{
321
					// Discard cache
322
					this._clear_cache();
323
324
					// Calendar is the current application, refresh now
325
					if(framework.activeApp.appName === this.appname)
326
					{
327
						this.setState({state: this.state});
328
					}
329
					// Bind once to trigger a refresh when tab is activated again
330
					else if(framework.applications.calendar && framework.applications.calendar.tab &&
331
						framework.applications.calendar.tab.contentDiv)
332
					{
333
						jQuery(framework.applications.calendar.tab.contentDiv)
334
							.off('show.calendar')
335
							.one('show.calendar',
336
								jQuery.proxy(function() {this.setState({state: this.state});},this)
337
							);
338
					}
339
				}
340
				break;
341
			case 'calendar':
342
				// Regular refresh
343
				var event = false;
344
				if(_id)
345
				{
346
					event = egw.dataGetUIDdata('calendar::'+_id);
347
				}
348
				if(event && event.data && event.data.date || _type === 'delete')
349
				{
350
					// Intelligent refresh without reloading everything
351
					var recurrences = Object.keys(egw.dataSearchUIDs(new RegExp('^calendar::'+_id+':')));
352
					var ids = event && event.data && event.data.recur_type && typeof _id === 'string' && _id.indexOf(':') < 0 || recurrences.length ?
353
						recurrences :
354
						['calendar::'+_id];
355
356
					if(_type === 'delete')
357
					{
358
						for(var i in ids)
359
						{
360
							egw.dataStoreUID(ids[i], null);
361
						}
362
					}
363
					// Updates are handled by events themselves through egw.data
364
					else if (_type !== 'update')
365
					{
366
						this._update_events(this.state, ids);
367
					}
368
					return false;
369
				}
370
				else
371
				{
372
					this._clear_cache();
373
374
					// Force redraw to current state
375
					this.setState({state: this.state});
376
					return false;
377
				}
378
				break;
0 ignored issues
show
Unused Code introduced by
This break statement is unnecessary and may be removed.
Loading history...
379
		}
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
380
	},
381
382
	/**
383
	 * Link hander for jDots template to just reload our iframe, instead of reloading whole admin app
384
	 *
385
	 * @param {String} _url
386
	 * @return {boolean|string} true, if linkHandler took care of link, false for default processing or url to navigate to
387
	 */
388
	linkHandler: function(_url)
389
	{
390
		if (_url == 'about:blank' || _url.match('menuaction=preferences\.preferences_categories_ui\.index'))
391
		{
392
			return false;
393
		}
394
		if (_url.match('menuaction=calendar\.calendar_uiviews\.'))
395
		{
396
			var view = _url.match(/calendar_uiviews\.([^&?]+)/);
397
			view = view && view.length > 1 ? view[1] : null;
398
399
			// Get query
400
			var q = {};
401
			_url.split('?')[1].split('&').forEach(function(i){
402
				q[i.split('=')[0]]=unescape(i.split('=')[1]);
403
			});
404
			delete q.ajax;
405
			delete q.menuaction;
406
			if(!view && q.view || q.view != view && view == 'index') view = q.view;
407
408
			// No specific view requested, looks like a reload from framework
409
			if(this.sidebox_et2 && typeof view === 'undefined')
410
			{
411
				this._clear_cache();
412
				this.setState({state: this.state});
413
				return false;
414
			}
415
416
			if (this.sidebox_et2 && typeof app.classes.calendar.views[view] == 'undefined' && view != 'index')
417
			{
418
				if(q.owner)
419
				{
420
					q.owner = q.owner.split(',');
421
					q.owner = q.owner.reduce(function(p,c) {if(p.indexOf(c)<0) p.push(c);return p;},[]);
422
					q.owner = q.owner.join(',');
423
				}
424
				q.menuaction = 'calendar.calendar_uiviews.index';
425
				this.sidebox_et2.getWidgetById('iframe').set_src(egw.link('/index.php',q));
426
				jQuery(this.sidebox_et2.parentNode).show();
427
				return true;
428
			}
429
			// Known AJAX view
430
			else if(app.classes.calendar.views[view])
431
			{
432
				// Reload of known view?
433
				if(view == 'index')
434
				{
435
					var pref = this.egw.preference('saved_states','calendar');
436
					view = pref.view || 'day';
437
				}
438
				// View etemplate not loaded
439
				if(typeof app.classes.calendar.views[view].etemplates[0] == 'string')
440
				{
441
					return _url + '&ajax=true';
442
				}
443
				// Already loaded, we'll just apply any variables to our current state
444
				var set = jQuery.extend({view: view},q);
445
				this.update_state(set);
446
				return true;
447
			}
448
		}
449
		else if (this.sidebox_et2)
450
		{
451
			var iframe = this.sidebox_et2.getWidgetById('iframe');
452
			if(!iframe) return false;
453
			iframe.set_src(_url);
454
			jQuery(this.sidebox_et2.parentNode).show();
455
			// Hide other views
456
			for(var _view in app.classes.calendar.views)
457
			{
458
				for(var i = 0; i < app.classes.calendar.views[_view].etemplates.length; i++)
459
				{
460
					jQuery(app.classes.calendar.views[_view].etemplates[i].DOMContainer).hide();
461
				}
462
			}
463
			this.state.view = '';
464
			return true;
465
		}
466
		// can not load our own index page, has to be done by framework
467
		return false;
468
	},
469
470
	/**
471
	 * Handle actions from the toolbar
472
	 *
473
	 * @param {egwAction} action Action from the toolbar
474
	 */
475
	toolbar_action: function toolbar_action(action)
476
	{
477
		// Most can just provide state change data
478
		if(action.data && action.data.state)
479
		{
480
			var state = jQuery.extend({},action.data.state);
481
			if(state.view == 'planner' && app.calendar.state.view != 'planner') {
482
				state.planner_view = app.calendar.state.view;
483
			}
484
			this.update_state(state);
485
		}
486
		// Special handling
487
		switch(action.id)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
488
		{
489
			case 'add':
490
				return egw.open(null,"calendar","add", {start: app.calendar.state.first});
491
			case 'weekend':
492
				this.update_state({weekend: action.checked});
493
				break;
494
			case 'today':
495
				var tempDate = new Date();
496
				var today = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(),0,-tempDate.getTimezoneOffset(),0);
497
				var change = {date: today.toJSON()};
498
				app.calendar.update_state(change);
499
				break;
500
			case 'next':
501
			case 'previous':
502
				var delta = action.id == 'previous' ? -1 : 1;
503
				var view = app.classes.calendar.views[app.calendar.state.view] || false;
504
				var start = new Date(app.calendar.state.date);
505
				if (view)
506
				{
507
					start = view.scroll(delta);
508
					app.calendar.update_state({date:app.calendar.date.toString(start)});
509
				}
510
				break;
511
		}
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
512
	},
513
514
	/**
515
	 * Set the app header
516
	 *
517
	 * Because the toolbar takes some vertical space and has some horizontal space,
518
	 * we don't use the system app header, but our own that is in the toolbar
519
	 *
520
	 * @param {string} header Text to display
521
	 */
522
	set_app_header: function(header) {
523
		var template = etemplate2.getById('calendar-toolbar');
524
		var widget = template ? template.widgetContainer.getWidgetById('app_header') : false;
525
		if(widget)
526
		{
527
			widget.set_value(header);
528
			egw_app_header('','calendar');
529
		}
530
		else
531
		{
532
			egw_app_header(header,'calendar');
533
		}
534
	},
535
536
	/**
537
	 * Setup and handle sortable calendars.
538
	 *
539
	 * You can only sort calendars if there is more than one owner, and the calendars
540
	 * are not combined (many owners, multi-week or month views)
541
	 * @returns {undefined}
542
	 */
543
	_sortable: function() {
544
		// Calender current state
545
		var state = this.getState();
546
		// Day / month sortables
547
		var daily = jQuery('#calendar-view_view .calendar_calGridHeader > div:first');
548
		var weekly = jQuery('#calendar-view_view tbody');
549
		if(state.view == 'day')
550
		{
551
			var sortable = daily;
552
			if(weekly.sortable('instance')) weekly.sortable('disable');
553
		}
554
		else
555
		{
556
			var sortable = weekly;
557
			if(daily.sortable('instance')) daily.sortable('disable');
558
		}
559
		if(!sortable.sortable('instance'))
560
		{
561
			sortable.sortable({
562
				cancel: "#divAppboxHeader, .calendar_calWeekNavHeader, .calendar_plannerHeader",
563
				handle: '.calendar_calGridHeader',
564
				//placeholder: "srotable_cal_wk_ph",
565
				axis:"y",
566
				revert: true,
567
				helper:"clone",
568
				create: function ()
569
				{
570
					var $sortItem = jQuery(this);
571
				},
572
				start: function (event, ui)
573
				{
574
					jQuery('.calendar_calTimeGrid',ui.helper).css('position', 'absolute');
575
					// Put owners into row IDs
576
					app.classes.calendar.views[app.calendar.state.view].etemplates[0].widgetContainer.iterateOver(function(widget) {
577
						if(widget.options.owner && !widget.disabled)
578
						{
579
							widget.div.parents('tr').attr('data-owner',widget.options.owner);
580
						}
581
						else
582
						{
583
							widget.div.parents('tr').removeAttr('data-owner');
584
						}
585
					},this,et2_calendar_timegrid);
586
				},
587
				stop: function ()
588
				{
589
				},
590
				update: function ()
591
				{
592
					var state = app.calendar.getState();
593
					if (state && typeof state.owner !== 'undefined')
594
					{
595
						var sortedArr = sortable.sortable('toArray', {attribute:"data-owner"});
596
						// No duplicates, no empties
597
						sortedArr = sortedArr.filter(function(value, index, self) {
598
							return value !== '' && self.indexOf(value) === index;
599
						});
600
601
						var parent = null;
602
						var children = [];
603
						if(state.view == 'day')
604
						{
605
							// If in day view, the days need to be re-ordered, avoiding
606
							// the current sort order
607
							app.classes.calendar.views.day.etemplates[0].widgetContainer.iterateOver(function(widget) {
608
								var idx = sortedArr.indexOf(widget.options.owner.toString());
609
								// Move the event holding div
610
								widget.set_left((parseInt(widget.options.width) * idx) + 'px');
611
								// Re-order the children, or it won't stay
612
								parent = widget._parent;
613
								children.splice(idx,0,widget);
614
							},this,et2_calendar_daycol);
615
							parent.day_widgets.sort(function(a,b) {
616
								return children.indexOf(a) - children.indexOf(b);
617
							});
618
						}
619
						else
620
						{
621
							// Re-order the children, or it won't stay
622
							app.classes.calendar.views.day.etemplates[0].widgetContainer.iterateOver(function(widget) {
623
								parent = widget._parent;
624
								var idx = sortedArr.indexOf(widget.options.owner);
625
								children.splice(idx,0,widget);
626
								widget.resize();
627
							},this,et2_calendar_timegrid);
628
						}
629
						parent._children.sort(function(a,b) {
630
							return children.indexOf(a) - children.indexOf(b);
631
						});
632
						// Directly update, since there is no other changes needed,
633
						// and we don't want the current sort order applied
634
						app.calendar.state.owner = sortedArr;
635
						parent.options.owner = sortedArr;
636
					}
637
				}
638
			});
639
		}
640
641
		// Enable or disable
642
		if(state.owner.length > 1 && (
643
			state.view == 'day' && state.owner.length < parseInt(egw.preference('day_consolidate','calendar')) ||
644
			state.view == 'week' && state.owner.length < parseInt(egw.preference('week_consolidate','calendar'))
645
		))
646
		{
647
			sortable.sortable('enable')
648
				.sortable("refresh")
649
				.disableSelection();
650
			var options = {};
651
			switch (state.view)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
652
			{
653
				case "day":
654
					options = {
655
						placeholder:"srotable_cal_day_ph",
656
						axis:"x",
657
						handle: '> div:first',
658
						helper: function(event, element) {
659
							var scroll = element.parentsUntil('.calendar_calTimeGrid').last().next();
660
							var helper = jQuery(document.createElement('div'))
661
								.append(element.clone())
662
								.css('height',scroll.parent().css('height'))
663
								.css('background-color','white')
664
								.css('width', element.css('width'));
665
							return helper;
666
						}
667
					};
668
					sortable.sortable('option', options);
669
					break;
670
				case "week":
671
					options = {
672
						placeholder:"srotable_cal_wk_ph",
673
						axis:"y",
674
						handle: '.calendar_calGridHeader',
675
						helper: 'clone'
676
					};
677
					sortable.sortable('option', options);
678
					break;
679
			}
680
		}
681
		else
682
		{
683
			sortable.sortable('disable');
684
		}
685
	},
686
687
	/**
688
	 * Bind scroll event
689
	 * When the user scrolls, we'll move enddate - startdate days
690
	 */
691
	_scroll: function() {
692
		/**
693
		 * Function we can pass all this off to
694
		 *
695
		 * @param {String} direction up, down, left or right
696
		 * @param {number} delta Integer for how many we're moving, should be +/- 1
697
		 */
698
		var scroll_animate = function(direction, delta)
699
		{
700
			// Scrolling too fast?
701
			if(app.calendar._scroll_disabled) return;
702
703
			// Find the template
704
			var id = jQuery(this).closest('.et2_container').attr('id');
705
			if(id)
706
			{
707
				var template = etemplate2.getById(id);
708
			}
709
			else
710
			{
711
				template = app.classes.calendar.views[app.calendar.state.view].etemplates[0];
712
			}
713
			if(!template) return;
714
715
			// Prevent scrolling too fast
716
			app.calendar._scroll_disabled = true;
717
718
			// Animate the transition, if possible
719
			var widget = null;
720
			template.widgetContainer.iterateOver(function(w) {
721
				if (w.getDOMNode() == this) widget = w;
722
			},this,et2_widget);
723
			if(widget == null)
724
			{
725
				template.widgetContainer.iterateOver(function(w) {
726
					widget = w;
727
				},this, et2_calendar_timegrid);
728
				if(widget == null) return;
729
			}
730
			/* Disabled
731
			 *
732
			// We clone the nodes so we can animate the transition
733
			var original = jQuery(widget.getDOMNode()).closest('.et2_grid');
734
			var cloned = original.clone(true).attr("id","CLONE");
735
736
			// Moving this stuff around scrolls things around too
737
			// We need this later
738
			var scrollTop = jQuery('.calendar_calTimeGridScroll',original).scrollTop();
739
740
			// This is to hide the scrollbar
741
			var wrapper = original.parent();
742
			if(direction == "right" || direction == "left")
743
			{
744
				original.css({"display":"inline-block","width":original.width()+"px"});
745
				cloned.css({"display":"inline-block","width":original.width()+"px"});
746
			}
747
			else
748
			{
749
				original.css("height",original.height() + "px");
750
				cloned.css("height",original.height() + "px");
751
			}
752
			var original_size = {height: wrapper.parent().css('height'), width: wrapper.parent().css('width')};
753
			wrapper.parent().css({overflow:'hidden', height:original.outerHeight()+"px", width:original.outerWidth() + "px"});
754
			wrapper.height(direction == "up" || direction == "down" ? 2 * original.outerHeight()  : original.outerHeight());
755
			wrapper.width(direction == "left" || direction == "right" ? 2 * original.outerWidth() : original.outerWidth());
756
757
			// Re-scroll to previous to avoid "jumping"
758
			jQuery('.calendar_calTimeGridScroll',original).scrollTop(scrollTop);
759
			switch(direction)
760
			{
761
				case "up":
762
				case "left":
763
					// Scrolling up
764
					// Apply the reverse quickly, then let it animate as the changes are
765
					// removed, leaving things where they should be.
766
767
					original.parent().append(cloned);
768
					// Makes it jump to destination
769
					wrapper.css({
770
						"transition-duration": "0s",
771
						"transition-delay": "0s",
772
						"transform": direction == "up" ? "translateY(-50%)" : "translateX(-50%)"
773
					});
774
					// Stop browser from caching style by forcing reflow
775
					if(wrapper[0]) wrapper[0].offsetHeight;
776
777
					wrapper.css({
778
						"transition-duration": "",
779
						"transition-delay": ""
780
					});
781
					break;
782
				case "down":
783
				case "right":
784
					// Scrolling down
785
					original.parent().prepend(cloned);
786
					break;
787
			}
788
			// Scroll clone to match to avoid "jumping"
789
			jQuery('.calendar_calTimeGridScroll',cloned).scrollTop(scrollTop);
790
791
			// Remove
792
			var remove = function() {
793
				// Starting animation
794
				wrapper.addClass("calendar_slide");
795
				var translate = direction == "down" ? "translateY(-50%)" : (direction == "right" ? "translateX(-50%)" : "");
796
				wrapper.css({"transform": translate});
797
				window.setTimeout(function() {
798
799
					cloned.remove();
800
801
					// Makes it jump to destination
802
					wrapper.css({
803
						"transition-duration": "0s",
804
						"transition-delay": "0s"
805
					});
806
807
					// Clean up from animation
808
					wrapper
809
						.removeClass("calendar_slide")
810
						.css({"transform": '',height: '', width:'',overflow:''});
811
					wrapper.parent().css({overflow: '', width: original_size.width, height: original_size.height});
812
					original.css("display","");
813
					if(wrapper.length)
814
					{
815
						wrapper[0].offsetHeight;
816
					}
817
					wrapper.css({
818
						"transition-duration": "",
819
						"transition-delay": ""
820
					});
821
822
					// Re-scroll to start of day
823
					template.widgetContainer.iterateOver(function(w) {
824
						w.resizeTimes();
825
					},this, et2_calendar_timegrid);
826
827
					window.setTimeout(function() {
828
						if(app.calendar)
829
						{
830
							app.calendar._scroll_disabled = false;
831
						}
832
					}, 100);
833
				},2000);
834
			}
835
			// If detecting the transition end worked, we wouldn't need to use a timeout.
836
			window.setTimeout(remove,100);
837
			*/
838
		   window.setTimeout(function() {
839
				if(app.calendar)
840
				{
841
					app.calendar._scroll_disabled = false;
842
				}
843
			}, 2000);
844
			// Get the view to calculate - this actually loads the new data
845
			// Using a timeout make it a little faster (in Chrome)
846
			window.setTimeout(function() {
847
				var view = app.classes.calendar.views[app.calendar.state.view] || false;
848
				var start = new Date(app.calendar.state.date);
849
				if (view && view.etemplates.indexOf(template) !== -1)
850
				{
851
					start = view.scroll(delta);
852
					app.calendar.update_state({date:app.calendar.date.toString(start)});
853
				}
854
				else
855
				{
856
					// Home - always 1 week
857
					// TODO
858
					return false;
859
				}
860
			},0);
861
		};
862
863
		// Bind only once, to the whole thing
864
		/* Disabled
865
		jQuery('body').off('.calendar')
866
			//.on('wheel','.et2_container:#calendar-list,#calendar-sidebox)',
867
			.on('wheel.calendar','.et2_container .calendar_calTimeGrid, .et2_container .calendar_plannerWidget',
868
				function(e)
869
				{
870
					// Consume scroll if in the middle of something
871
					if(app.calendar._scroll_disabled) return false;
872
873
					// Ignore if they're going the other way
874
					var direction = e.originalEvent.deltaY > 0 ? 1 : -1;
875
					var at_bottom = direction !== -1;
876
					var at_top = direction !== 1;
877
878
					jQuery(this).children(":not(.calendar_calGridHeader)").each(function() {
879
						// Check for less than 2px from edge, as sometimes we can't scroll anymore, but still have
880
						// 2px left to go
881
						at_bottom = at_bottom && Math.abs(this.scrollTop - (this.scrollHeight - this.offsetHeight)) <= 2;
882
					}).each(function() {
883
						at_top = at_top && this.scrollTop === 0;
884
					});
885
					if(!at_bottom && !at_top) return;
886
887
					e.preventDefault();
888
889
					scroll_animate.call(this, direction > 0 ? "down" : "up", direction);
890
891
					return false;
892
				}
893
			);
894
		*/
895
		if(typeof framework !== 'undefined' && framework.applications.calendar && framework.applications.calendar.tab)
896
		{
897
			jQuery(framework.applications.calendar.tab.contentDiv)
898
				.swipe('destroy');
899
900
			jQuery(framework.applications.calendar.tab.contentDiv)
901
				.swipe({
902
					//Generic swipe handler for all directions
903
					swipe:function(event, direction, distance, duration, fingerCount) {
904
						if(direction == "up" || direction == "down")
905
						{
906
							if(fingerCount <= 1) return;
907
							var at_bottom = direction !== -1;
908
							var at_top = direction !== 1;
909
910
							jQuery(this).children(":not(.calendar_calGridHeader)").each(function() {
911
								// Check for less than 2px from edge, as sometimes we can't scroll anymore, but still have
912
								// 2px left to go
913
								at_bottom = at_bottom && Math.abs(this.scrollTop - (this.scrollHeight - this.offsetHeight)) <= 2;
914
							}).each(function() {
915
								at_top = at_top && this.scrollTop === 0;
916
							});
917
						}
918
919
						var delta = direction == "down" || direction == "right" ? -1 : 1;
920
						// But we animate in the opposite direction to the swipe
921
						var opposite = {"down": "up", "up": "down", "left": "right", "right": "left"};
922
						direction = opposite[direction];
923
						scroll_animate.call(jQuery(event.target).closest('.calendar_calTimeGrid, .calendar_plannerWidget')[0], direction, delta);
924
						return false;
925
					},
926
					allowPageScroll: jQuery.fn.swipe.pageScroll.VERTICAL,
927
					threshold: 100,
928
					fallbackToMouseEvents: false,
929
					triggerOnTouchEnd: false
930
				});
931
932
			// Page up & page down
933
			egw_registerGlobalShortcut(jQuery.ui.keyCode.PAGE_UP, false, false, false, function() {
934
				if(app.calendar.state.view == 'listview')
935
				{
936
					return false;
937
				}
938
				scroll_animate.call(this,"up", -1);
939
				return true;
940
			});
941
			egw_registerGlobalShortcut(jQuery.ui.keyCode.PAGE_DOWN, false, false, false, function() {
942
				if(app.calendar.state.view == 'listview')
943
				{
944
					return false;
945
				}
946
				scroll_animate.call(this,"down", 1);
947
				return true;
948
			});
949
		}
950
	},
951
952
	/**
953
	 * Handler for changes generated by internal user interactions, like
954
	 * drag & drop inside calendar and resize.
955
	 *
956
	 * @param {Event} event
957
	 * @param {et2_calendar_event} widget Widget for the event
958
	 * @param {string} dialog_button - 'single', 'series', or 'exception', based on the user's answer
959
	 *	in the popup
960
	 * @returns {undefined}
961
	 */
962
	event_change: function(event, widget, dialog_button)
963
	{
964
		// Add loading spinner - not visible if the body / gradient is there though
965
		widget.div.addClass('loading');
966
967
		// Integrated infolog event
968
		//Get infologID if in case if it's an integrated infolog event
969
		if (widget.options.value.app == 'infolog')
970
		{
971
			// If it is an integrated infolog event we need to edit infolog entry
972
			egw().json(
973
				'stylite_infolog_calendar_integration::ajax_moveInfologEvent',
974
				[widget.options.value.app_id, widget.options.value.start, widget.options.value.duration],
975
				// Remove loading spinner
976
				function() {if(widget.div) widget.div.removeClass('loading');}
977
			).sendRequest();
978
		}
979
		else
980
		{
981
			var _send = function() {
982
				egw().json(
983
					'calendar.calendar_uiforms.ajax_moveEvent',
984
					[
985
						dialog_button == 'exception' ? widget.options.value.app_id : widget.options.value.id,
986
						widget.options.value.owner,
987
						widget.options.value.start,
988
						widget.options.value.owner,
989
						widget.options.value.duration,
990
						dialog_button == 'series' ? widget.options.value.start : null
991
					],
992
					// Remove loading spinner
993
					function() {if(widget && widget.div) widget.div.removeClass('loading');}
994
				).sendRequest(true);
995
			};
996
			if(dialog_button == 'series' && widget.options.value.recur_type)
997
			{
998
				widget.series_split_prompt(function(_button_id)
999
					{
1000
						if (_button_id == et2_dialog.OK_BUTTON)
1001
						{
1002
							_send();
1003
						}
1004
					}
1005
				);
1006
			}
1007
			else
1008
			{
1009
				_send();
1010
			}
1011
		}
1012
	},
1013
1014
	/**
1015
	 * open the freetime search popup
1016
	 *
1017
	 * @param {string} _link
1018
	 */
1019
	freetime_search_popup: function(_link)
1020
	{
1021
		this.egw.open_link(_link,'ft_search','700x500') ;
1022
	},
1023
1024
	/**
1025
	 * send an ajax request to server to set the freetimesearch window content
1026
	 *
1027
	 */
1028
	freetime_search: function()
1029
	{
1030
		var content = this.et2.getArrayMgr('content').data;
1031
		content['start'] = this.et2.getWidgetById('start').get_value();
1032
		content['end'] = this.et2.getWidgetById('end').get_value();
1033
		content['duration'] = this.et2.getWidgetById('duration').get_value();
1034
1035
		var request = this.egw.json('calendar.calendar_uiforms.ajax_freetimesearch', [content],null,null,null,null);
1036
		request.sendRequest();
1037
	},
1038
1039
	/**
1040
	 * Function for disabling the recur_data multiselect box
1041
	 *
1042
	 */
1043
	check_recur_type: function()
1044
	{
1045
		var recurType = this.et2.getWidgetById('recur_type');
1046
		var recurData = this.et2.getWidgetById('recur_data');
1047
1048
		if(recurType && recurData)
1049
		{
1050
			recurData.set_disabled(recurType.get_value() != 2 && recurType.get_value() != 4);
1051
		}
1052
	},
1053
1054
	/**
1055
	 * Actions for when the user changes the event start date in edit dialog
1056
	 *
1057
	 * @returns {undefined}
1058
	 */
1059
	edit_start_change: function(input, widget)
1060
	{
1061
		if(!widget)
1062
		{
1063
			widget = etemplate2.getById('calendar-edit').widgetContainer.getWidgetById('start');
1064
		}
1065
1066
		// Update settings for querying participants
1067
		this.edit_update_participant(widget);
1068
1069
		// Update recurring date limit, if not set it can't be before start
1070
		if(widget)
1071
		{
1072
			var recur_end = widget.getRoot().getWidgetById('recur_enddate');
1073
			if(recur_end && !recur_end.getValue())
1074
			{
1075
				recur_end.set_min(widget.getValue());
1076
			}
1077
		}
1078
		// Update currently selected alarm time
1079
		this.alarm_custom_date();
1080
	},
1081
1082
	/**
1083
	 * Show/Hide end date, for both edit and freetimesearch popups,
1084
	 * based on if "use end date" selected or not.
1085
	 *
1086
	 */
1087
	set_enddate_visibility: function()
1088
	{
1089
		var duration = this.et2.getWidgetById('duration');
1090
		var start = this.et2.getWidgetById('start');
1091
		var end = this.et2.getWidgetById('end');
1092
		var content = this.et2.getArrayMgr('content').data;
1093
1094
		if (typeof duration != 'undefined' && typeof end != 'undefined')
1095
		{
1096
			end.set_disabled(duration.get_value()!=='');
1097
1098
			// Only set end date if not provided, adding seconds fails with DST
1099
			if (!end.disabled && !content.end)
1100
			{
1101
				end.set_value(start.get_value());
1102
				if (typeof content.duration != 'undefined') end.set_value("+"+content.duration);
1103
			}
1104
		}
1105
		this.edit_update_participant(start);
1106
	},
1107
1108
	/**
1109
	 * Update query parameters for participants
1110
	 *
1111
	 * This allows for resource conflict checking
1112
	 *
1113
	 * @param {DOMNode|et2_widget} input Either the input node, or the widget
1114
	 * @param {et2_widget} [widget] If input is an input node, widget will have
1115
	 *	the widget, otherwise it will be undefined.
1116
	 */
1117
	edit_update_participant: function(input, widget)
1118
	{
1119
		if(typeof widget === 'undefined') widget = input;
1120
		var content = widget.getInstanceManager().getValues(widget.getRoot());
1121
		var participant = widget.getRoot().getWidgetById('participant');
1122
1123
		participant.set_autocomplete_params({exec:{
1124
			start: content.start,
1125
			end: content.end,
1126
			duration: content.duration,
1127
			whole_day: content.whole_day,
1128
		}});
1129
	},
1130
1131
	/**
1132
	 * handles actions selectbox in calendar edit popup
1133
	 *
1134
	 * @param {mixed} _event
1135
	 * @param {et2_base_widget} widget "actions selectBox" in edit popup window
1136
	 */
1137
	actions_change: function(_event, widget)
1138
	{
1139
		var event = this.et2.getArrayMgr('content').data;
1140
		if (widget)
1141
		{
1142
			var id = this.et2.getArrayMgr('content').data['id'];
1143
			switch (widget.get_value())
1144
			{
1145
				case 'print':
1146
					this.egw.open_link('calendar.calendar_uiforms.edit&cal_id='+id+'&print=1','_blank','700x700');
1147
					break;
1148
				case 'mail':
1149
					this.egw.json('calendar.calendar_uiforms.ajax_custom_mail', [event, !event['id'], false],null,null,null,null).sendRequest();
1150
					this.et2._inst.submit();
1151
					break;
1152
				case 'sendrequest':
1153
					this.egw.json('calendar.calendar_uiforms.ajax_custom_mail', [event, !event['id'], true],null,null,null,null).sendRequest();
1154
					this.et2._inst.submit();
1155
					break;
1156
				case 'infolog':
1157
					this.egw.open_link('infolog.infolog_ui.edit&action=calendar&action_id='+(jQuery.isPlainObject(event)?event['id']:event),'_blank','700x600','infolog');
1158
					this.et2._inst.submit();
1159
					break;
1160
				case 'ical':
1161
					this.et2._inst.postSubmit();
1162
					break;
1163
				default:
1164
					this.et2._inst.submit();
1165
			}
1166
		}
1167
	},
1168
1169
	/**
1170
	 * open mail compose popup window
1171
	 *
1172
	 * @param {Array} vars
1173
	 * @todo need to provide right mail compose from server to custom_mail function
1174
	 */
1175
	custom_mail: function (vars)
1176
	{
1177
		this.egw.open_link(this.egw.link("/index.php",vars),'_blank','700x700');
1178
	},
1179
1180
	/**
1181
	 * control delete_series popup visibility
1182
	 *
1183
	 * @param {et2_widget} widget
1184
	 * @param {Array} exceptions an array contains number of exception entries
1185
	 *
1186
	 */
1187
	delete_btn: function(widget,exceptions)
1188
	{
1189
		var content = this.et2.getArrayMgr('content').data;
1190
1191
		if (exceptions)
1192
		{
1193
			var buttons = [
1194
				{
1195
					button_id: 'keep',
1196
					title: this.egw.lang('All exceptions are converted into single events.'),
1197
					text: this.egw.lang('Keep exceptions'),
1198
					id: 'button[delete_keep_exceptions]',
1199
					image: 'keep', "default":true
1200
				},
1201
				{
1202
					button_id: 'delete',
1203
					title: this.egw.lang('The exceptions are deleted together with the series.'),
1204
					text: this.egw.lang('Delete exceptions'),
1205
					id: 'button[delete_exceptions]',
1206
					image: 'delete'
1207
				},
1208
				{
1209
					button_id: 'cancel',
1210
					text: this.egw.lang('Cancel'),
1211
					id: 'dialog[cancel]',
1212
					image: 'cancel'
1213
				}
1214
1215
			];
1216
			var self = this;
1217
			et2_dialog.show_dialog
1218
			(
1219
					function(_button_id)
1220
					{
1221
						if (_button_id != 'dialog[cancel]')
1222
						{
1223
							widget.getRoot().getWidgetById('delete_exceptions').set_value(_button_id == 'button[delete_exceptions]');
1224
							widget.getInstanceManager().submit('button[delete]');
1225
							return true;
1226
						}
1227
						else
1228
						{
1229
							return false;
1230
						}
1231
					},
1232
					this.egw.lang("Do you want to keep the series exceptions in your calendar?"),
1233
					this.egw.lang("This event is part of a series"), {}, buttons , et2_dialog.WARNING_MESSAGE
1234
			);
1235
		}
1236
		else if (content['recur_type'] !== 0)
1237
		{
1238
			et2_dialog.confirm(widget,'Delete this series of recuring events','Delete Series');
1239
		}
1240
		else
1241
		{
1242
			et2_dialog.confirm(widget,'Delete this event','Delete');
1243
		}
1244
	},
1245
1246
	/**
1247
	 * On change participant event, try to set add button status based on
1248
	 * participant field value. Additionally, disable/enable quantity field
1249
	 * if there's none resource value or there are more than one resource selected.
1250
	 *
1251
	 */
1252
	participantOnChange: function ()
1253
	{
1254
		var add = this.et2.getWidgetById('add');
1255
		var quantity = this.et2.getWidgetById('quantity');
1256
		var participant = this.et2.getWidgetById('participant');
1257
1258
		// array of participants
1259
		var value = participant.get_value();
1260
1261
		add.set_readonly(value.length <= 0);
1262
1263
		quantity.set_readonly(false);
1264
1265
		// number of resources
1266
		var nRes = 0;
1267
1268
		for (var i=0;i<value.length;i++)
1269
		{
1270
			if (!value[i].match(/\D/ig) || nRes)
1271
			{
1272
				quantity.set_readonly(true);
1273
				quantity.set_value(1);
1274
			}
1275
			nRes++;
1276
		}
1277
	},
1278
1279
	/**
1280
	 * print_participants_status(egw,widget)
1281
	 * Handle to apply changes from status in print popup
1282
	 *
1283
	 * @param {mixed} _event
1284
	 * @param {et2_base_widget} widget widget "status" in print popup window
1285
	 *
1286
	 */
1287
	print_participants_status: function(_event, widget)
1288
	{
1289
		if (widget && window.opener)
1290
		{
1291
			//Parent popup window
1292
			var editPopWindow = window.opener;
1293
1294
			if (editPopWindow)
1295
			{
1296
				//Update paretn popup window
1297
				editPopWindow.etemplate2.getByApplication('calendar')[0].widgetContainer.getWidgetById(widget.id).set_value(widget.get_value());
1298
			}
1299
			this.et2._inst.submit();
1300
1301
			editPopWindow.opener.egw_refresh('status changed','calendar');
1302
		}
1303
		else if (widget)
1304
		{
1305
			window.egw_refresh(this.egw.lang('The original popup edit window is closed! You need to close the print window and reopen the entry again.'),'calendar');
1306
		}
1307
	},
1308
1309
	/**
1310
	 * Handles to select freetime, and replace the selected one on Start,
1311
	 * and End date&time in edit calendar entry popup.
1312
	 *
1313
	 * @param {mixed} _event
1314
	 * @param {et2_base_widget} _widget widget "select button" in freetime search popup window
1315
	 *
1316
	 */
1317
	freetime_select: function(_event, _widget)
1318
	{
1319
		if (_widget)
1320
		{
1321
			var content = this.et2._inst.widgetContainer.getArrayMgr('content').data;
1322
			// Make the Id from selected button by checking the index
1323
			var selectedId = _widget.id.match(/^select\[([0-9])\]$/i)[1];
1324
1325
			var sTime = this.et2.getWidgetById(selectedId+'start');
1326
1327
			//check the parent window is still open before to try to access it
1328
			if (window.opener && sTime)
1329
			{
1330
				var editWindowObj = window.opener.etemplate2.getByApplication('calendar')[0];
1331
				if (typeof editWindowObj != "undefined")
1332
				{
1333
					var startTime = editWindowObj.widgetContainer.getWidgetById('start');
1334
					var endTime = editWindowObj.widgetContainer.getWidgetById('end');
1335
					if (startTime && endTime)
1336
					{
1337
						startTime.set_value(sTime.get_value());
1338
						endTime.set_value(sTime.get_value());
1339
						endTime.set_value('+'+content['duration']);
1340
					}
1341
				}
1342
			}
1343
			else
1344
			{
1345
				alert(this.egw.lang('The original calendar edit popup is closed!'));
1346
			}
1347
		}
1348
		egw(window).close();
1349
	},
1350
1351
	/**
1352
	 * show/hide the filter of nm list in calendar listview
1353
	 *
1354
	 */
1355
	filter_change: function()
1356
	{
1357
		var view = app.classes.calendar.views['listview'].etemplates[0].widgetContainer || false;
1358
		var filter = view ? view.getWidgetById('nm').getWidgetById('filter') : null;
1359
		var dates = view ? view.getWidgetById('calendar.list.dates') : null;
1360
1361
		// Update state when user changes it
1362
		if(filter)
1363
		{
1364
			app.calendar.state.filter = filter.getValue();
1365
			// Change sort order for before - this is just the UI, server does the query
1366
			if(app.calendar.state.filter == 'before')
1367
			{
1368
				view.getWidgetById('nm').sortBy('cal_start',false, false);
1369
			}
1370
			else
1371
			{
1372
				view.getWidgetById('nm').sortBy('cal_start',true, false);
1373
			}
1374
		}
1375
		else
1376
		{
1377
			delete app.calendar.state.filter;
1378
		}
1379
		if (filter && dates)
1380
		{
1381
			dates.set_disabled(filter.value !== "custom");
1382
			if (filter.value == "custom" && !this.state_update_in_progress)
1383
			{
1384
				// Copy state dates over, without causing [another] state update
1385
				var actual = this.state_update_in_progress;
1386
				this.state_update_in_progress = true;
1387
				view.getWidgetById('startdate').set_value(app.calendar.state.first);
1388
				view.getWidgetById('enddate').set_value(app.calendar.state.last);
1389
				this.state_update_in_progress = actual;
1390
1391
				jQuery(view.getWidgetById('startdate').getDOMNode()).find('input').focus();
1392
			}
1393
		}
1394
	},
1395
1396
	/**
1397
	 * Application links from non-list events
1398
	 *
1399
	 * @param {egwAction} _action
1400
	 * @param {egwActionObject[]} _events
1401
	 */
1402
	action_open: function(_action, _events)
1403
	{
1404
		var id = _events[0].id.split('::');
1405
		if(_action.data.open)
1406
		{
1407
			var open = JSON.parse(_action.data.open) || {};
1408
			var extra = open.extra || '';
1409
1410
			extra = extra.replace(/(\$|%24)app/,id[0]).replace(/(\$|%24)id/,id[1]);
1411
1412
			// Get a little smarter with the context
1413
			if(!extra)
1414
			{
1415
				var context = {};
1416
				if(egw.dataGetUIDdata(_events[0].id) && egw.dataGetUIDdata(_events[0].id).data)
1417
				{
1418
					// Found data in global cache
1419
					context = egw.dataGetUIDdata(_events[0].id).data;
1420
					extra = {};
1421
				}
1422
				else if (_events[0].iface.getWidget() && _events[0].iface.getWidget().instanceOf(et2_valueWidget))
1423
				{
1424
					// Able to extract something from the widget
1425
					context = _events[0].iface.getWidget().getValue ?
1426
						_events[0].iface.getWidget().getValue() :
1427
						_events[0].iface.getWidget().options.value || {};
1428
					extra = {};
1429
				}
1430
				// Try to pull whatever we can from the event
1431
				else if (jQuery.isEmptyObject(context) && _action.menu_context && (_action.menu_context.event.target))
1432
				{
1433
					var target = _action.menu_context.event.target;
1434
					while(target != null && target.parentNode && jQuery.isEmptyObject(target.dataset))
1435
					{
1436
						target = target.parentNode;
1437
					}
1438
1439
					context = extra = jQuery.extend({},target.dataset);
1440
					var owner = jQuery(target).closest('[data-owner]').get(0);
1441
					if(owner && owner.dataset.owner && owner.dataset.owner != this.state.owner)
1442
					{
1443
						extra.owner = owner.dataset.owner.split(',');
1444
					}
1445
				}
1446
				if(context.date) extra.date = context.date;
1447
				if(context.app) extra.app = context.app;
1448
				if(context.app_id) extra.app_id = context.app_id;
1449
			}
1450
1451
			this.egw.open(open.id_data||'',open.app,open.type,extra ? extra : context);
0 ignored issues
show
Bug introduced by
The variable context does not seem to be initialized in case !extra on line 1413 is false. Are you sure this can never be the case?
Loading history...
1452
		}
1453
		else if (_action.data.url)
1454
		{
1455
			var url = _action.data.url;
1456
			url = url.replace(/(\$|%24)app/,id[0]).replace(/(\$|%24)id/,id[1]);
1457
			this.egw.open_link(url);
1458
		}
1459
	},
1460
1461
	/**
1462
	 * Context menu action (on a single event) in non-listview to generate ical
1463
	 *
1464
	 * Since nextmatch is all ready to handle that, we pass it through
1465
	 *
1466
	 * @param {egwAction} _action
1467
	 * @param {egwActionObject[]} _events
1468
	 */
1469
	ical: function(_action, _events)
1470
	{
1471
		// Send it through nextmatch
1472
		_action.data.nextmatch = etemplate2.getById('calendar-list').widgetContainer.getWidgetById('nm');
1473
		var ids = {ids:[]};
1474
		for(var i = 0; i < _events.length; i++)
1475
		{
1476
			ids.ids.push(_events[i].id);
1477
		}
1478
		nm_action(_action, _events, null, ids);
1479
	},
1480
1481
	/**
1482
	 * Change status (via AJAX)
1483
	 *
1484
	 * @param {egwAction} _action
1485
	 * @param {egwActionObject} _events
1486
	 */
1487
	status: function(_action, _events)
1488
	{
1489
		// Should be a single event, but we'll do it for all
1490
		for(var i = 0; i < _events.length; i++)
1491
		{
1492
			var event_widget = _events[i].iface.getWidget() || false;
1493
			if(!event_widget) continue;
1494
1495
			event_widget.recur_prompt(jQuery.proxy(function(button_id,event_data) {
1496
				switch(button_id)
1497
				{
1498
					case 'exception':
1499
						egw().json(
1500
							'calendar.calendar_uiforms.ajax_status',
1501
							[event_data.app_id, egw.user('account_id'), _action.data.id]
1502
						).sendRequest(true);
1503
						break;
1504
					case 'series':
1505
					case 'single':
1506
						egw().json(
1507
							'calendar.calendar_uiforms.ajax_status',
1508
							[event_data.id, egw.user('account_id'), _action.data.id]
1509
						).sendRequest(true);
1510
						break;
1511
					case 'cancel':
1512
					default:
1513
						break;
1514
				}
1515
			},this));
1516
		}
1517
1518
	},
1519
1520
	/**
1521
	 * this function try to fix ids which are from integrated apps
1522
	 *
1523
	 * @param {egwAction} _action
1524
	 * @param {egwActionObject[]} _senders
1525
	 */
1526
	cal_fix_app_id: function(_action, _senders)
1527
	{
1528
		var app = 'calendar';
1529
		var id = _senders[0].id;
1530
		var matches = id.match(/^(?:calendar::)?([0-9]+)(:([0-9]+))?$/);
1531
		if (matches)
1532
		{
1533
			id = matches[1];
1534
		}
1535
		else
1536
		{
1537
			matches = id.match(/^([a-z_-]+)([0-9]+)/i);
1538
			if (matches)
1539
			{
1540
				app = matches[1];
1541
				id = matches[2];
1542
			}
1543
		}
1544
		var backup_url = _action.data.url;
1545
1546
		_action.data.url = _action.data.url.replace(/(\$|%24)id/,id);
1547
		_action.data.url = _action.data.url.replace(/(\$|%24)app/,app);
1548
1549
		nm_action(_action, _senders,false,{ids:[id]});
1550
1551
		_action.data.url = backup_url;	// restore url
1552
	},
1553
1554
	/**
1555
	 * Open calendar entry, taking into accout the calendar integration of other apps
1556
	 *
1557
	 * calendar_uilist::get_rows sets var js_calendar_integration object
1558
	 *
1559
	 * @param _action
1560
	 * @param _senders
1561
	 *
1562
	 */
1563
	cal_open: function(_action, _senders)
1564
	{
1565
		// Try for easy way - find a widget
1566
		if(_senders[0].iface.getWidget)
1567
		{
1568
			var widget = _senders[0].iface.getWidget();
1569
			return widget.recur_prompt();
1570
		}
1571
1572
		// Nextmatch in list view does not have a widget, but we can pull
1573
		// the data by ID
1574
		// Check for series
1575
		var id = _senders[0].id;
1576
		var data = egw.dataGetUIDdata(id);
1577
		if (data && data.data)
1578
		{
1579
			et2_calendar_event.recur_prompt(data.data);
1580
			return;
1581
		}
1582
		var matches = id.match(/^(?:calendar::)?([0-9]+):([0-9]+)$/);
1583
1584
		// Check for other app integration data sent from server
1585
		var backup = _action.data;
1586
		if(_action.parent.data && _action.parent.data.nextmatch)
1587
		{
1588
			var js_integration_data = _action.parent.data.nextmatch.options.settings.js_integration_data || this.et2.getArrayMgr('content').data.nm.js_integration_data;
1589
			if(typeof js_integration_data == 'string')
1590
			{
1591
				js_integration_data = JSON.parse(js_integration_data);
1592
			}
1593
		}
1594
		matches = id.match(/^calendar::([a-z_-]+)([0-9]+)/i);
1595
		if (matches && js_integration_data && js_integration_data[matches[1]])
1596
		{
1597
			var app = matches[1];
1598
			_action.data.url = window.egw_webserverUrl+'/index.php?';
1599
			var get_params = js_integration_data[app].edit;
1600
			get_params[js_integration_data[app].edit_id] = matches[2];
1601
			for(var name in get_params)
1602
				_action.data.url += name+"="+encodeURIComponent(get_params[name])+"&";
1603
1604
			if (js_integration_data[app].edit_popup)
1605
			{
1606
				egw.open_link(_action.data.url,'_blank',js_integration_data[app].edit_popup,app);
1607
1608
				_action.data = backup;	// restore url, width, height, nm_action
1609
				return;
1610
			}
1611
		}
1612
		else
1613
		{
1614
			// Other app integration using link registry
1615
			var data = egw.dataGetUIDdata(_senders[0].id);
1616
			if(data && data.data)
1617
			{
1618
				return egw.open(data.data.app_id, data.data.app, 'edit');
1619
			}
1620
		}
1621
		// Regular, single event
1622
		egw.open(id.replace(/^calendar::/g,''),'calendar','edit');
1623
	},
1624
1625
	/**
1626
	 * Delete (a single) calendar entry over ajax.
1627
	 *
1628
	 * Used for the non-list views
1629
	 *
1630
	 * @param {egwAction} _action
1631
	 * @param {egwActionObject} _events
1632
	 */
1633
	delete: function(_action, _events)
1634
	{
1635
		// Should be a single event, but we'll do it for all
1636
		for(var i = 0; i < _events.length; i++)
1637
		{
1638
			var event_widget = _events[i].iface.getWidget() || false;
1639
			if(!event_widget) continue;
1640
1641
			event_widget.recur_prompt(jQuery.proxy(function(button_id,event_data) {
1642
				switch(button_id)
1643
				{
1644
					case 'exception':
1645
						egw().json(
1646
							'calendar.calendar_uiforms.ajax_delete',
1647
							[event_data.app_id]
1648
						).sendRequest(true);
1649
						break;
1650
					case 'series':
1651
					case 'single':
1652
						egw().json(
1653
							'calendar.calendar_uiforms.ajax_delete',
1654
							[event_data.id]
1655
						).sendRequest(true);
1656
						break;
1657
					case 'cancel':
1658
					default:
1659
						break;
1660
				}
1661
			},this));
1662
		}
1663
	},
1664
1665
	/**
1666
	 * Delete calendar entry, asking if you want to delete series or exception
1667
	 *
1668
	 * Used for nextmatch
1669
	 *
1670
	 * @param _action
1671
	 * @param _senders
1672
	 */
1673
	cal_delete: function(_action, _senders)
1674
	{
1675
		var backup = _action.data;
1676
		var matches = false;
1677
1678
		// Loop so we ask if any of the selected entries is part of a series
1679
		for(var i = 0; i < _senders.length; i++)
1680
		{
1681
			var id = _senders[i].id;
1682
			if(!matches)
1683
			{
1684
				matches = id.match(/^(?:calendar::)?([0-9]+):([0-9]+)$/);
1685
			}
1686
		}
1687
		if (matches)
1688
		{
1689
			var popup = jQuery('#calendar-list_delete_popup').get(0);
1690
			if (typeof popup != 'undefined')
1691
			{
1692
				// nm action - show popup
1693
				nm_open_popup(_action,_senders);
1694
			}
1695
			return;
1696
		}
1697
1698
		nm_action(_action, _senders);
1699
	},
1700
1701
	/**
1702
	 * Confirmation dialog for moving a series entry
1703
	 *
1704
	 * @param {object} _DOM
1705
	 * @param {et2_widget} _button button Save | Apply
1706
	 */
1707
	move_edit_series: function(_DOM,_button)
1708
	{
1709
		var content = this.et2.getArrayMgr('content').data;
1710
		var start_date = this.et2.getWidgetById('start').get_value();
1711
		var end_date = this.et2.getWidgetById('end').get_value();
1712
		var whole_day = this.et2.getWidgetById('whole_day');
1713
		var duration = ''+this.et2.getWidgetById('duration').get_value();
1714
		var is_whole_day = whole_day && whole_day.get_value() == whole_day.options.selected_value;
1715
		var button = _button;
1716
		var that = this;
1717
1718
		var instance_date = window.location.search.match(/date=(\d{4}-\d{2}-\d{2}(?:.+Z)?)/);
1719
		if(instance_date && instance_date.length && instance_date[1])
1720
		{
1721
			instance_date = new Date(unescape(instance_date[1]));
1722
			instance_date.setUTCMinutes(instance_date.getUTCMinutes() +instance_date.getTimezoneOffset());
1723
		}
1724
		if (typeof content != 'undefined' && content.id != null &&
1725
			typeof content.recur_type != 'undefined' && content.recur_type != null && content.recur_type != 0
1726
		)
1727
		{
1728
			if (content.start != start_date ||
1729
				content.whole_day != is_whole_day ||
1730
				(duration && ''+content.duration != duration ||
1731
				// End date might ignore seconds, and be 59 seconds off for all day events
1732
				!duration && Math.abs(new Date(end_date) - new Date(content.end)) > 60000)
1733
			)
1734
			{
1735
				et2_calendar_event.series_split_prompt(
1736
					content, instance_date, function(_button_id)
1737
					{
1738
						if (_button_id == et2_dialog.OK_BUTTON)
1739
						{
1740
							that.et2._inst.submit(button);
1741
1742
						}
1743
					}
1744
				);
1745
			}
1746
			else
1747
			{
1748
				return true;
1749
			}
1750
		}
1751
		else
1752
		{
1753
			return true;
1754
		}
1755
	},
1756
1757
	/**
1758
	 * Sidebox merge
1759
	 *
1760
	 * Manage the state and pass the request to the correct place.  Since the nextmatch
1761
	 * and the sidebox have different ideas of the 'current' timespan (sidebox
1762
	 * always has a start and end date) we need to call merge on the nextmatch
1763
	 * if the current view is listview, so the user gets the results they expect.
1764
	 *
1765
	 * @param {Event} event UI event
1766
	 * @param {et2_widget} widget Should be the merge selectbox
1767
	 */
1768
	sidebox_merge: function(event, widget)
1769
	{
1770
		if(!widget || !widget.getValue()) return false;
1771
1772
		if(this.state.view == 'listview')
1773
		{
1774
			// If user is looking at the list, pretend they used the context
1775
			// menu and process it through the nextmatch
1776
			var nm = etemplate2.getById('calendar-list').widgetContainer.getWidgetById('nm') || false;
1777
			var selected = nm ? nm.controller._objectManager.getSelectedLinks() : [];
1778
			var action = nm.controller._actionManager.getActionById('document_'+widget.getValue());
1779
			if(nm && (!selected || !selected.length))
1780
			{
1781
				nm.controller._selectionMgr.selectAll(true);
1782
			}
1783
			if(action && selected)
1784
			{
1785
				action.execute(selected);
1786
			}
1787
		}
1788
		else
1789
		{
1790
			// Set the hidden inputs to the current time span & submit
1791
			widget.getRoot().getWidgetById('first').set_value(app.calendar.state.first);
1792
			widget.getRoot().getWidgetById('last').set_value(app.calendar.state.last);
1793
			widget.getInstanceManager().postSubmit();
1794
		}
1795
		window.setTimeout(function() {widget.set_value('');},100);
1796
1797
		return false;
1798
	},
1799
1800
	/**
1801
	 * Method to set state for JSON requests (jdots ajax_exec or et2 submits can NOT use egw.js script tag)
1802
	 *
1803
	 * @param {object} _state
1804
	 */
1805
	set_state: function(_state)
1806
	{
1807
		if (typeof _state == 'object')
1808
		{
1809
			// If everything is loaded, handle the changes
1810
			if(this.sidebox_et2 !== null)
1811
			{
1812
				this.update_state(_state);
1813
			}
1814
			else
1815
			{
1816
				// Things aren't loaded yet, just set it
1817
				this.state = _state;
1818
			}
1819
		}
1820
	},
1821
1822
	/**
1823
	 * Change only part of the current state.
1824
	 *
1825
	 * The passed state options (filters) are merged with the current state, so
1826
	 * this is the one that should be used for most calls, as setState() requires
1827
	 * the complete state.
1828
	 *
1829
	 * @param {Object} _set New settings
1830
	 */
1831
	update_state: function update_state(_set)
1832
	{
1833
		// Make sure we're running in top window
1834
		if(window !== window.top && window.top.app.calendar)
1835
		{
1836
			return window.top.app.calendar.update_state(_set);
1837
		}
1838
		if(this.state_update_in_progress) return;
1839
1840
		var changed = [];
1841
		var new_state = jQuery.extend({}, this.state);
1842
		if (typeof _set === 'object')
1843
		{
1844
			for(var s in _set)
1845
			{
1846
				if (new_state[s] !== _set[s] && (typeof new_state[s] == 'string' || typeof new_state[s] !== 'string' && new_state[s]+'' !== _set[s]+''))
1847
				{
1848
					changed.push(s + ': ' + new_state[s] + ' -> ' + _set[s]);
1849
					new_state[s] = _set[s];
1850
				}
1851
			}
1852
		}
1853
		if(changed.length && !this.state_update_in_progress)
1854
		{
1855
			// This activates calendar app if you call setState from a different app
1856
			// such as home.  If we change state while not active, sizing is wrong.
1857
			if(typeof framework !== 'undefined' && framework.applications.calendar && framework.applications.calendar.hasSideboxMenuContent)
1858
			{
1859
				framework.setActiveApp(framework.applications.calendar);
1860
			}
1861
1862
			console.log('Calendar state changed',changed.join("\n"));
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1863
			// Log
1864
			this.egw.debug('navigation','Calendar state changed', changed.join("\n"));
1865
			this.setState({state: new_state});
1866
		}
1867
	},
1868
1869
	/**
1870
	 * Return state object defining current view
1871
	 *
1872
	 * Called by favorites to query current state.
1873
	 *
1874
	 * @return {object} description
1875
	 */
1876
	getState: function getState()
1877
	{
1878
		var state = jQuery.extend({},this.state);
1879
1880
		if (!state)
1881
		{
1882
			var egw_script_tag = document.getElementById('egw_script_id');
1883
			state = egw_script_tag.getAttribute('data-calendar-state');
1884
			state = state ? JSON.parse(state) : {};
1885
		}
1886
1887
		// Don't store current user in state to allow admins to create favourites for all
1888
		// Should make no difference for normal users.
1889
		if(state.owner == egw.user('account_id'))
1890
		{
1891
			// 0 is always the current user, so if an admin creates a default favorite,
1892
			// it will work for other users too.
1893
			state.owner = 0;
1894
		}
1895
1896
		// Keywords are only for list view
1897
		if(state.view == 'listview')
1898
		{
1899
			var listview = app.classes.calendar.views.listview.etemplates[0] &&
1900
				app.classes.calendar.views.listview.etemplates[0].widgetContainer &&
1901
				app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm');
1902
			if(listview && listview.activeFilters && listview.activeFilters.search)
1903
			{
1904
				state.keywords = listview.activeFilters.search;
1905
			}
1906
		}
1907
1908
		// Don't store date or first and last
1909
		delete state.date;
1910
		delete state.first;
1911
		delete state.last;
1912
		delete state.startdate;
1913
		delete state.enddate;
1914
		delete state.start_date;
1915
		delete state.end_date;
1916
1917
		return state;
1918
	},
1919
1920
	/**
1921
	 * Set a state previously returned by getState
1922
	 *
1923
	 * Called by favorites to set a state saved as favorite.
1924
	 *
1925
	 * @param {object} state containing "name" attribute to be used as "favorite" GET parameter to a nextmatch
1926
	 */
1927
	setState: function setState(state)
1928
	{
1929
		// State should be an object, not a string, but we'll parse
1930
		if(typeof state == "string")
1931
		{
1932
			if(state.indexOf('{') != -1 || state =='null')
1933
			{
1934
				state = JSON.parse(state);
1935
			}
1936
		}
1937
		if(typeof state.state !== 'object' || !state.state.view)
1938
		{
1939
			state.state = {view: 'week'};
1940
		}
1941
		// States with no name (favorites other than No filters) default to
1942
		// today.  Applying a favorite should keep the current date.
1943
		if(!state.state.date)
1944
		{
1945
			state.state.date = state.name ? this.state.date : new Date();
1946
		}
1947
		if(typeof state.state.weekend == 'undefined')
1948
		{
1949
			state.state.weekend = true;
1950
		}
1951
1952
		// Hide other views
1953
		var view = app.classes.calendar.views[state.state.view];
1954
		for(var _view in app.classes.calendar.views)
1955
		{
1956
			if(state.state.view != _view && app.classes.calendar.views[_view])
1957
			{
1958
				for(var i = 0; i < app.classes.calendar.views[_view].etemplates.length; i++)
1959
				{
1960
					if(typeof app.classes.calendar.views[_view].etemplates[i] !== 'string' &&
1961
						view.etemplates.indexOf(app.classes.calendar.views[_view].etemplates[i]) == -1)
1962
					{
1963
						jQuery(app.classes.calendar.views[_view].etemplates[i].DOMContainer).hide();
1964
					}
1965
				}
1966
			}
1967
		}
1968
		if(this.sidebox_et2)
1969
		{
1970
			jQuery(this.sidebox_et2.getInstanceManager().DOMContainer).hide();
1971
		}
1972
1973
		// Check for valid cache
1974
		var cachable_changes = ['date','weekend','view','days','planner_view','sortby'];
1975
		var keys = jQuery.unique(Object.keys(this.state).concat(Object.keys(state.state)));
1976
		for(var i = 0; i < keys.length; i++)
1977
		{
1978
			var s = keys[i];
1979
			if (this.state[s] !== state.state[s])
1980
			{
1981
				if(cachable_changes.indexOf(s) === -1)
1982
				{
1983
					// Expire daywise cache
1984
					var daywise = egw.dataKnownUIDs(app.classes.calendar.DAYWISE_CACHE_ID);
1985
1986
					// Can't delete from here, as that would disconnect the existing widgets listening
1987
					for(var i = 0; i < daywise.length; i++)
1988
					{
1989
						egw.dataStoreUID(app.classes.calendar.DAYWISE_CACHE_ID + '::' + daywise[i],null);
1990
					}
1991
					break;
1992
				}
1993
			}
1994
		}
1995
1996
		// Check for a supported client-side view
1997
		if(app.classes.calendar.views[state.state.view] &&
1998
			// Check that the view is instanciated
1999
			typeof app.classes.calendar.views[state.state.view].etemplates[0] !== 'string' && app.classes.calendar.views[state.state.view].etemplates[0].widgetContainer
2000
		)
2001
		{
2002
			// Doing an update - this includes the selected view, and the sidebox
2003
			// We set a flag to ignore changes from the sidebox which would
2004
			// cause infinite loops.
2005
			this.state_update_in_progress = true;
2006
2007
			// Sanitize owner so it's always an array
2008
			if(state.state.owner === null || !state.state.owner ||
2009
				(typeof state.state.owner.length != 'undefined' && state.state.owner.length == 0)
2010
			)
2011
			{
2012
				state.state.owner = undefined;
2013
			}
2014
			switch(typeof state.state.owner)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
2015
			{
2016
				case 'undefined':
2017
					state.state.owner = [this.egw.user('account_id')];
2018
					break;
2019
				case 'string':
2020
					state.state.owner = state.state.owner.split(',');
2021
					break;
2022
				case 'number':
2023
					state.state.owner = [state.state.owner];
2024
					break;
2025
				case 'object':
2026
					// An array-like Object or an Array?
2027
					if(!state.state.owner.filter)
2028
					{
2029
						state.state.owner = jQuery.map(state.state.owner, function(owner) {return owner;});
2030
					}
2031
			}
2032
			// Remove duplicates
2033
			state.state.owner = state.state.owner.filter(function(value, index, self) {
2034
				return self.indexOf(value) === index;
2035
			});
2036
			// Make sure they're all strings
2037
			state.state.owner = state.state.owner.map(function(owner) { return ''+owner;});
2038
			// Keep sort order
2039
			if(typeof this.state.owner === 'object')
2040
			{
2041
				var owner = [];
2042
				this.state.owner.forEach(function(key) {
2043
					var found = false;
2044
					state.state.owner = state.state.owner.filter(function(item) {
2045
						if(!found && item == key) {
2046
							owner.push(item);
2047
							found = true;
2048
							return false;
2049
						} else
2050
							return true;
2051
					});
2052
				});
2053
				// Add in any new owners
2054
				state.state.owner = owner.concat(state.state.owner);
2055
			}
2056
			if (state.state.owner.indexOf('0') >= 0)
2057
			{
2058
				state.state.owner[state.state.owner.indexOf('0')] = this.egw.user('account_id');
2059
			}
2060
2061
			// Show the correct number of grids
2062
			var grid_count = 0;
2063
			switch(state.state.view)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
2064
			{
2065
				case 'day':
2066
					grid_count = 1;
2067
					break;
2068
				case 'day4':
2069
				case 'week':
2070
					grid_count = state.state.owner.length >= parseInt(this.egw.preference('week_consolidate','calendar')) ? 1 : state.state.owner.length;
2071
					break;
2072
				case 'weekN':
2073
					grid_count = parseInt(this.egw.preference('multiple_weeks','calendar')) || 3;
2074
					break;
2075
				// Month is calculated individually for the month
2076
			}
2077
2078
			var grid = view.etemplates[0].widgetContainer.getWidgetById('view');
2079
2080
			// Show the templates for the current view
2081
			// Needs to be visible while updating so sizing works
2082
			for(var i = 0; i < view.etemplates.length; i++)
2083
			{
2084
				jQuery(view.etemplates[i].DOMContainer).show();
2085
			}
2086
2087
			/*
2088
			If the count is different, we need to have the correct number
2089
			If the count is > 1, it's either because there are multiple date spans (weekN, month) and we need the correct span
2090
			per row, or there are multiple owners and we need the correct owner per row.
2091
			*/
2092
			if(grid)
2093
			{
2094
				// Show loading div to hide redrawing
2095
				egw.loading_prompt(
2096
					this.appname,true,egw.lang('please wait...'),
2097
					typeof framework !== 'undefined' ? framework.applications.calendar.tab.contentDiv : false,
2098
					egwIsMobile()?'horizental':'spinner'
2099
				);
2100
2101
				var loading = false;
2102
2103
2104
				var value = [];
2105
				state.state.first = view.start_date(state.state).toJSON();
2106
				// We'll modify this one, so it needs to be a new object
2107
				var date = new Date(state.state.first);
2108
2109
				// Hide all but the first day header
2110
				jQuery(grid.getDOMNode()).toggleClass(
2111
					'hideDayColHeader',
2112
					state.state.view == 'week' || state.state.view == 'day4'
2113
				);
2114
2115
				// Determine the different end date & varying values
2116
				switch(state.state.view)
2117
				{
2118
					case 'month':
2119
						var end = state.state.last = view.end_date(state.state);
2120
						grid_count = Math.ceil((end - date) / (1000 * 60 * 60 * 24) / 7);
2121
						// fall through
2122
					case 'weekN':
2123
						for(var week = 0; week < grid_count; week++)
2124
						{
2125
							var val = {
2126
								id: app.classes.calendar._daywise_cache_id(date,state.state.owner),
2127
								start_date: date.toJSON(),
2128
								end_date: new Date(date.toJSON()),
2129
								owner: state.state.owner
2130
							};
2131
							val.end_date.setUTCHours(24*7-1);
2132
							val.end_date.setUTCMinutes(59);
2133
							val.end_date.setUTCSeconds(59);
2134
							val.end_date = val.end_date.toJSON();
2135
							value.push(val);
2136
							date.setUTCHours(24*7);
2137
						}
2138
						state.state.last=val.end_date;
2139
						break;
2140
					case 'day':
2141
						var end = state.state.last = view.end_date(state.state).toJSON();
2142
							value.push({
2143
							id: app.classes.calendar._daywise_cache_id(date,state.state.owner),
2144
								start_date: state.state.first,
2145
								end_date: state.state.last,
2146
								owner: view.owner(state.state)
2147
							});
2148
						break;
2149
					default:
2150
						var end = state.state.last = view.end_date(state.state).toJSON();
2151
						for(var owner = 0; owner < grid_count && owner < state.state.owner.length; owner++)
2152
						{
2153
							var _owner = grid_count > 1 ? state.state.owner[owner] || 0 : state.state.owner;
2154
							value.push({
2155
								id: app.classes.calendar._daywise_cache_id(date,_owner),
2156
								start_date: date,
2157
								end_date: end,
2158
								owner: _owner
2159
							});
2160
						}
2161
						break;
2162
				}
2163
				// If we have cached data for the timespan, pass it along
2164
				// Single day with multiple owners still needs owners split to satisfy
2165
				// caching keys, otherwise they'll fetch & cache consolidated
2166
				if(state.state.view == 'day' && state.state.owner.length < parseInt(this.egw.preference('day_consolidate','calendar')))
2167
				{
2168
					var day_value = [];
2169
					for(var i = 0; i < state.state.owner.length; i++)
2170
					{
2171
						day_value.push({
2172
							start_date: state.state.first,
2173
							end_date: state.state.last,
2174
							owner: state.state.owner[i]
2175
						});
2176
					}
2177
					loading = this._need_data(day_value,state.state);
2178
				}
2179
				else
2180
				{
2181
					loading = this._need_data(value,state.state);
2182
				}
2183
2184
				var row_index = 0;
2185
2186
				// Find any matching, existing rows - they can be kept
2187
				grid.iterateOver(function(widget) {
2188
					for(var i = 0; i < value.length; i++)
2189
					{
2190
						if(widget.id == value[i].id)
2191
						{
2192
							// Keep it, but move it
2193
							if(i > row_index)
2194
							{
2195
								for(var j = i-row_index; j > 0; j--)
2196
								{
2197
									// Move from the end to the start
2198
									grid._children.unshift(grid._children.pop());
2199
2200
									// Swap DOM nodes
2201
									var a = grid._children[0].getDOMNode().parentNode.parentNode;
2202
									var a_scroll = jQuery('.calendar_calTimeGridScroll',a).scrollTop();
2203
									var b = grid._children[1].getDOMNode().parentNode.parentNode;
2204
									a.parentNode.insertBefore(a,b);
2205
2206
									// Moving nodes changes scrolling, so set it back
2207
									var a_scroll = jQuery('.calendar_calTimeGridScroll',a).scrollTop(a_scroll);
2208
								}
2209
							}
2210
							else if (row_index > i)
2211
							{
2212
								// Swap DOM nodes
2213
								var a = grid._children[row_index].getDOMNode().parentNode.parentNode;
2214
								var a_scroll = jQuery('.calendar_calTimeGridScroll',a).scrollTop();
2215
								var b = grid._children[i].getDOMNode().parentNode.parentNode;
2216
2217
								// Simple scroll forward, put top on the bottom
2218
								// This makes it faster if they scroll back next
2219
								if(i==0 && row_index == 1)
2220
								{
2221
									jQuery(b).appendTo(b.parentNode);
2222
									grid._children.push(grid._children.shift());
2223
								}
2224
								else
2225
								{
2226
									grid._children.splice(i,0,widget);
2227
									grid._children.splice(row_index+1,1);
2228
									a.parentNode.insertBefore(a,b);
2229
								}
2230
2231
								// Moving nodes changes scrolling, so set it back
2232
								var a_scroll = jQuery('.calendar_calTimeGridScroll',a).scrollTop(a_scroll);
2233
							}
2234
							break;
2235
						}
2236
					}
2237
					row_index++;
2238
				},this,et2_calendar_view);
2239
				row_index = 0;
2240
2241
				// Set rows that need it
2242
				grid.iterateOver(function(widget) {
2243
					var was_disabled = false;
2244
					if(row_index < value.length)
2245
					{
2246
						was_disabled = widget.options.disabled;
2247
						widget.set_disabled(false);
2248
					}
2249
					else
2250
					{
2251
						widget.set_disabled(true);
2252
						return;
2253
					}
2254
					if(widget.set_show_weekend)
2255
					{
2256
						widget.set_show_weekend(view.show_weekend(state.state));
2257
					}
2258
					if(widget.set_granularity)
2259
					{
2260
						if(widget.loader) widget.loader.show();
2261
						widget.set_granularity(view.granularity(state.state));
2262
					}
2263
					if(widget.id == value[row_index].id &&
2264
						widget.get_end_date().getUTCFullYear() == value[row_index].end_date.substring(0,4) &&
2265
						widget.get_end_date().getUTCMonth()+1 == value[row_index].end_date.substring(5,7) &&
2266
						widget.get_end_date().getUTCDate() == value[row_index].end_date.substring(8,10)
2267
					)
2268
					{
2269
						// Do not need to re-set this row, but we do need to re-do
2270
						// the times, as they may have changed
2271
						widget.resizeTimes();
2272
						window.setTimeout(jQuery.proxy(widget.set_header_classes, widget),0);
2273
2274
						// If disabled while the daycols were loaded, they won't load their events
2275
						for(var day = 0; was_disabled && day < widget.day_widgets.length; day++)
2276
						{
2277
							egw.dataStoreUID(
2278
									widget.day_widgets[day].registeredUID,
2279
								egw.dataGetUIDdata(widget.day_widgets[day].registeredUID).data
2280
							);
2281
						}
2282
						
2283
						// Hide loader
2284
						widget.loader.hide();
2285
						row_index++;
2286
						return;
2287
					}
2288
					if(widget.set_value)
2289
					{
2290
						widget.set_value(value[row_index++]);
2291
					}
2292
				},this, et2_calendar_view);
2293
			}
2294
			else if(state.state.view !== 'listview')
2295
			{
2296
				// Simple, easy case - just one widget for the selected time span. (planner)
2297
				// Update existing view's special attribute filters, defined in the view list
2298
				for(var updater in view)
2299
				{
2300
					if(typeof view[updater] === 'function')
2301
					{
2302
						var value = view[updater].call(this,state.state);
2303
						if(updater === 'start_date') state.state.first = this.date.toString(value);
2304
						if(updater === 'end_date') state.state.last = this.date.toString(value);
2305
2306
						// Set value
2307
						for(var i = 0; i < view.etemplates.length; i++)
2308
						{
2309
							view.etemplates[i].widgetContainer.iterateOver(function(widget) {
2310
								if(typeof widget['set_'+updater] === 'function')
2311
								{
2312
									widget['set_'+updater](value);
2313
								}
2314
							}, this, et2_calendar_view);
2315
						}
2316
					}
2317
				}
2318
				var value = [{start_date: state.state.first, end_date: state.state.last}];
2319
				loading = this._need_data(value,state.state);
2320
			}
2321
			// Include first & last dates in state, mostly for server side processing
2322
			if(state.state.first && state.state.first.toJSON) state.state.first = state.state.first.toJSON();
2323
			if(state.state.last && state.state.last.toJSON) state.state.last = state.state.last.toJSON();
2324
2325
			// Toggle todos
2326
			if((state.state.view == 'day' || this.state.view == 'day') && jQuery(view.etemplates[0].DOMContainer).is(':visible'))
2327
			{
2328
				if(state.state.view == 'day' && state.state.owner.length === 1 && !isNaN(state.state.owner) && state.state.owner[0] >= 0 && !egwIsMobile())
2329
				{
2330
					// Set width to 70%, otherwise if a scrollbar is needed for the view, it will conflict with the todo list
2331
					jQuery(app.classes.calendar.views.day.etemplates[0].DOMContainer).css("width","70%");
2332
					jQuery(view.etemplates[1].DOMContainer).css({"left":"70%", "height":(jQuery(framework.tabsUi.activeTab.contentDiv).height()-30)+'px'});
2333
					// TODO: Maybe some caching here
2334
					this.egw.jsonq('calendar_uiviews::ajax_get_todos', [state.state.date, state.state.owner[0]], function(data) {
2335
						this.getWidgetById('label').set_value(data.label||'');
2336
						this.getWidgetById('todos').set_value({content:data.todos||''});
2337
					},view.etemplates[1].widgetContainer);
2338
					view.etemplates[0].resize();
2339
				}
2340
				else
2341
				{
2342
					jQuery(app.classes.calendar.views.day.etemplates[1].DOMContainer).css("left","100%");
2343
					jQuery(app.classes.calendar.views.day.etemplates[1].DOMContainer).hide();
2344
					jQuery(app.classes.calendar.views.day.etemplates[0].DOMContainer).css("width","100%");
2345
					view.etemplates[0].widgetContainer.iterateOver(function(w) {
2346
						w.set_width('100%');
2347
					},this,et2_calendar_timegrid);
2348
				}
2349
			}
2350
			else if(jQuery(view.etemplates[0].DOMContainer).is(':visible'))
2351
			{
2352
				jQuery(view.etemplates[0].DOMContainer).css("width","");
2353
				view.etemplates[0].widgetContainer.iterateOver(function(w) {
2354
					w.set_width('100%');
2355
				},this,et2_calendar_timegrid);
2356
			}
2357
2358
			// List view (nextmatch) has slightly different fields
2359
			if(state.state.view === 'listview')
2360
			{
2361
				state.state.startdate = state.state.date;
2362
				if(state.state.startdate.toJSON)
2363
				{
2364
					state.state.startdate = state.state.startdate.toJSON();
2365
				}
2366
2367
				if(state.state.end_date)
2368
				{
2369
					state.state.enddate = state.state.end_date;
2370
				}
2371
				if(state.state.enddate && state.state.enddate.toJSON)
2372
				{
2373
					state.state.enddate = state.state.enddate.toJSON();
2374
				}
2375
				state.state.col_filter = {participant: state.state.owner};
2376
				state.state.search = state.state.keywords ? state.state.keywords : state.state.search;
2377
2378
2379
				var nm = view.etemplates[0].widgetContainer.getWidgetById('nm');
2380
2381
				// 'Custom' filter needs an end date
2382
				if(nm.activeFilters.filter === 'custom' && !state.state.end_date)
2383
				{
2384
					state.state.enddate = state.state.last;
2385
				}
2386
				if(state.state.enddate && state.state.startdate && state.state.startdate > state.state.enddate)
2387
				{
2388
					state.state.enddate = state.state.startdate;
2389
				}
2390
				nm.applyFilters(state.state);
2391
2392
				// Try to keep last value up to date with what's in nextmatch
2393
				if(nm.activeFilters.enddate)
2394
				{
2395
					this.state.last = nm.activeFilters.enddate;
2396
				}
2397
				// Updates the display of start & end date
2398
				this.filter_change();
2399
			}
2400
			else
2401
			{
2402
				// Turn off nextmatch's automatic stuff - it won't work while it
2403
				// is hidden, and can cause an infinite loop as it tries to layout.
2404
				// (It will automatically re-start when shown)
2405
				try
2406
				{
2407
					var nm = app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm');
2408
					nm.controller._grid.doInvalidate = false;
2409
				} catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
2410
				// Other views do not search
2411
				delete state.state.keywords;
2412
			}
2413
			this.state = jQuery.extend({},state.state);
2414
2415
			/* Update re-orderable calendars */
2416
			this._sortable();
2417
2418
			/* Update sidebox widgets to show current value*/
2419
			if(this.sidebox_hooked_templates.length)
2420
			{
2421
				for(var j = 0; j < this.sidebox_hooked_templates.length; j++)
2422
				{
2423
					var sidebox = this.sidebox_hooked_templates[j];
2424
					// Remove any destroyed or not valid templates
2425
					if(!sidebox.getInstanceManager || !sidebox.getInstanceManager())
2426
					{
2427
						this.sidebox_hooked_templates.splice(j,1,0);
2428
						continue;
2429
					}
2430
					sidebox.iterateOver(function(widget) {
2431
						if(widget.id == 'view')
2432
						{
2433
							// View widget has a list of state settings, which require special handling
2434
							for(var i = 0; i < widget.options.select_options.length; i++)
2435
							{
2436
								var option_state = JSON.parse(widget.options.select_options[i].value) || [];
2437
								var match = true;
2438
								for(var os_key in option_state)
2439
								{
2440
									// Sometimes an optional state variable is not yet defined (sortby, days, etc)
2441
									match = match && (option_state[os_key] == this.state[os_key] || typeof this.state[os_key] == 'undefined');
2442
								}
2443
								if(match)
2444
								{
2445
									widget.set_value(widget.options.select_options[i].value);
2446
									return;
2447
								}
2448
							}
2449
						}
2450
						else if (widget.id == 'keywords')
2451
						{
2452
							widget.set_value('');
2453
						}
2454
						else if(typeof state.state[widget.id] !== 'undefined' && state.state[widget.id] != widget.getValue())
2455
						{
2456
							// Update widget.  This may trigger an infinite loop of
2457
							// updates, so we do it after changing this.state and set a flag
2458
							try
2459
							{
2460
								widget.set_value(state.state[widget.id]);
2461
							}
2462
							catch(e)
2463
							{
2464
								widget.set_value('');
2465
							}
2466
						}
2467
						else if (widget.instanceOf(et2_inputWidget) && typeof state.state[widget.id] == 'undefined')
2468
						{
2469
							// No value, clear it
2470
							widget.set_value('');
2471
						}
2472
					},this,et2_valueWidget);
2473
				}
2474
			}
2475
2476
			// If current state matches a favorite, hightlight it
2477
			this.highlight_favorite();
2478
2479
			// Update app header
2480
			this.set_app_header(view.header(state.state));
2481
2482
			// Reset auto-refresh timer
2483
			this._set_autorefresh();
2484
2485
			// Sidebox is updated, we can clear the flag
2486
			this.state_update_in_progress = false;
2487
2488
			// Update saved state in preferences
2489
			var save = {};
2490
			for(var i = 0; i < this.states_to_save.length; i++)
2491
			{
2492
				save[this.states_to_save[i]] = this.state[this.states_to_save[i]];
2493
			}
2494
			egw.set_preference('calendar','saved_states', save);
2495
2496
			// Trigger resize to get correct sizes, as they may have sized while
2497
			// hidden
2498
			for(var i = 0; i < view.etemplates.length; i++)
2499
			{
2500
				view.etemplates[i].resize();
2501
			}
2502
2503
			// If we need to fetch data from the server, it will hide the loader
2504
			// when done but if everything is in the cache, hide from here.
2505
			if(!loading)
2506
			{
2507
				window.setTimeout(jQuery.proxy(function() {
2508
2509
					egw.loading_prompt(this.appname,false);
2510
				},this),500);
2511
			}
2512
2513
			return;
2514
		}
2515
		// old calendar state handling on server-side (incl. switching to and from listview)
2516
		var menuaction = 'calendar.calendar_uiviews.index';
2517
		if (typeof state.state != 'undefined' && (typeof state.state.view == 'undefined' || state.state.view == 'listview'))
2518
		{
2519
			if (state.name)
2520
			{
2521
				// 'blank' is the special name for no filters, send that instead of the nice translated name
2522
				state.state.favorite = jQuery.isEmptyObject(state) || jQuery.isEmptyObject(state.state||state.filter) ? 'blank' : state.name.replace(/[^A-Za-z0-9-_]/g, '_');
2523
				// set date for "No Filter" (blank) favorite to todays date
2524
				if (state.state.favorite == 'blank')
2525
					state.state.date = jQuery.datepicker.formatDate('yymmdd', new Date);
2526
			}
2527
			menuaction = 'calendar.calendar_uilist.listview';
2528
			state.state.ajax = 'true';
2529
			// check if we already use et2 / are in listview
2530
			if (this.et2 || etemplate2 && etemplate2.getByApplication('calendar'))
2531
			{
2532
				// current calendar-code can set regular calendar states only via a server-request :(
2533
				// --> check if we only need to set something which can be handeled by nm internally
2534
				// or we need a redirect
2535
				// ToDo: pass them via nm's get_rows call to server (eg. by passing state), so we dont need a redirect
2536
				var current_state = this.getState();
2537
				var need_redirect = false;
2538
				for(var attr in current_state)
2539
				{
2540
					switch(attr)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
2541
					{
2542
						case 'cat_id':
2543
						case 'owner':
2544
						case 'filter':
2545
							if (state.state[attr] != current_state[attr])
2546
							{
2547
								need_redirect = true;
2548
								// reset of attributes managed on server-side
2549
								if (state.state.favorite === 'blank')
2550
								{
2551
									switch(attr)
2552
									{
2553
										case 'cat_id':
2554
											state.state.cat_id = 0;
2555
											break;
2556
										case 'owner':
2557
											state.state.owner = egw.user('account_id');
2558
											break;
2559
										case 'filter':
2560
											state.state.filter = 'default';
2561
											break;
2562
									}
2563
								}
2564
								break;
2565
							}
2566
							break;
2567
2568
						case 'view':
2569
							// "No filter" (blank) favorite: if not in listview --> stay in that view
2570
							if (state.state.favorite === 'blank' && current_state.view != 'listview')
2571
							{
2572
								menuaction = 'calendar.calendar_uiviews.index';
2573
								delete state.state.ajax;
2574
								need_redirect = true;
2575
							}
2576
					}
2577
				}
2578
				if (!need_redirect)
2579
				{
2580
					return this._super.apply(this, [state]);
2581
				}
2582
			}
2583
		}
2584
		// setting internal state now, that linkHandler does not intercept switching from listview to any old view
2585
		this.state = jQuery.extend({},state.state);
2586
		if(this.sidebox_et2)
2587
		{
2588
			jQuery(this.sidebox_et2.getInstanceManager().DOMContainer).show();
2589
		}
2590
2591
		var query = jQuery.extend({menuaction: menuaction},state.state||{});
2592
2593
		// prepend an owner 0, to reset all owners and not just set given resource type
2594
		if(typeof query.owner != 'undefined')
2595
		{
2596
			query.owner = '0,'+ (typeof query.owner == 'object' ? query.owner.join(',') : (''+query.owner).replace('0,',''));
2597
		}
2598
2599
		this.egw.open_link(this.egw.link('/index.php',query), 'calendar');
2600
2601
		// Stop the normal bubbling if this is called on click
2602
		return false;
2603
	},
2604
2605
	/**
2606
	 * Check to see if any of the selected is an event widget
2607
	 * Used to separate grid actions from event actions
2608
	 *
2609
	 * @param {egwAction} _action
2610
	 * @param {egwActioObject[]} _selected
2611
	 * @returns {boolean} Is any of the selected an event widget
2612
	 */
2613
	is_event: function(_action, _selected)
2614
	{
2615
		var is_widget = false;
2616
		for(var i = 0; i < _selected.length; i++)
2617
		{
2618
			if(_selected[i].iface.getWidget() && _selected[i].iface.getWidget().instanceOf(et2_calendar_event))
2619
			{
2620
				is_widget = true;
2621
			}
2622
2623
			// Also check classes, usually indicating permission
2624
			if(_action.data && _action.data.enableClass)
2625
			{
2626
				is_widget = is_widget && (jQuery( _selected[i].iface.getDOMNode()).hasClass(_action.data.enableClass));
2627
			}
2628
			if(_action.data && _action.data.disableClass)
2629
			{
2630
				is_widget = is_widget && !(jQuery( _selected[i].iface.getDOMNode()).hasClass(_action.data.disableClass));
2631
			}
2632
2633
		}
2634
		return is_widget;
2635
	},
2636
2637
	/**
2638
	 * Enable/Disable custom Date-time for set Alarm
2639
	 *
2640
	 * @param {egw object} _egw
2641
	 * @param {widget object} _widget new_alarm[options] selectbox
2642
	 */
2643
	alarm_custom_date: function (_egw,_widget)
2644
	{
2645
		var alarm_date = this.et2.getWidgetById('new_alarm[date]');
2646
		var alarm_options = _widget || this.et2.getWidgetById('new_alarm[options]');
2647
		var start = this.et2.getWidgetById('start');
2648
2649
		if (alarm_date && alarm_options
2650
					&& start)
2651
		{
2652
			if (alarm_options.get_value() != '0')
2653
			{
2654
				alarm_date.set_class('calendar_alarm_date_display');
2655
			}
2656
			else
2657
			{
2658
				alarm_date.set_class('');
2659
			}
2660
			var startDate = typeof start.get_value != 'undefined'?start.get_value():start.value;
2661
			if (startDate)
2662
			{
2663
				var date = new Date(startDate);
2664
				date.setTime(date.getTime() - 1000 * parseInt(alarm_options.get_value()));
2665
				alarm_date.set_value(date);
2666
			}
2667
		}
2668
	},
2669
2670
	/**
2671
	 * Set alarm options based on WD/Regular event user preferences
2672
	 * Gets fired by wholeday checkbox
2673
	 *
2674
	 * @param {egw object} _egw
2675
	 * @param {widget object} _widget whole_day checkbox
2676
	 */
2677
	set_alarmOptions_WD: function (_egw,_widget)
2678
	{
2679
		var alarm = this.et2.getWidgetById('alarm');
2680
		if (!alarm) return;	// no default alarm
2681
		var content = this.et2.getArrayMgr('content').data;
2682
		var start = this.et2.getWidgetById('start');
2683
		var self= this;
2684
		var time = alarm.cells[1][0].widget;
2685
		var event = alarm.cells[1][1].widget;
2686
		// Convert a seconds of time to a translated label
2687
		var _secs_to_label = function (_secs)
2688
		{
2689
			var label='';
2690
			if (_secs <= 3600)
2691
			{
2692
				label = self.egw.lang('%1 minutes', _secs/60);
2693
			}
2694
			else if(_secs <= 86400)
2695
			{
2696
				label = self.egw.lang('%1 hours', _secs/3600);
2697
			}
2698
			return label;
2699
		};
2700
		if (typeof content['alarm'][1]['default'] == 'undefined')
2701
		{
2702
			// user deleted alarm --> nothing to do
2703
		}
2704
		else
2705
		{
2706
			var def_alarm = this.egw.preference(_widget.get_value() === "true" ?
2707
				'default-alarm-wholeday' : 'default-alarm', 'calendar');
2708
			if (!def_alarm && def_alarm !== 0)	// no alarm
2709
			{
2710
				jQuery('#calendar-edit_alarm > tbody :nth-child(1)').hide();
2711
			}
2712
			else
2713
			{
2714
				jQuery('#calendar-edit_alarm > tbody :nth-child(1)').show();
2715
				start.set_hours(0);
2716
				start.set_minutes(0);
2717
				time.set_value(start.get_value());
2718
				time.set_value('-'+(60 * def_alarm));
2719
				event.set_value(_secs_to_label(60 * def_alarm));
2720
			}
2721
		}
2722
	},
2723
2724
2725
	/**
2726
	 * Clear all calendar data from egw.data cache
2727
	 */
2728
	_clear_cache: function() {
2729
		// Full refresh, clear the caches
2730
		var events = egw.dataKnownUIDs('calendar');
2731
		for(var i = 0; i < events.length; i++)
2732
		{
2733
			egw.dataDeleteUID('calendar::' + events[i]);
2734
		}
2735
		var daywise = egw.dataKnownUIDs(app.classes.calendar.DAYWISE_CACHE_ID);
2736
		for(var i = 0; i < daywise.length; i++)
2737
		{
2738
			// Empty to clear existing widgets
2739
			egw.dataStoreUID(app.classes.calendar.DAYWISE_CACHE_ID + '::' + daywise[i], null);
2740
		}
2741
	},
2742
2743
	/**
2744
	 * Take the date range(s) in the value and decide if we need to fetch data
2745
	 * for the date ranges, or if they're already cached fill them in.
2746
	 *
2747
	 * @param {Object} value
2748
	 * @param {Object} state
2749
	 *
2750
	 * @return {boolean} Data was requested
2751
	 */
2752
	_need_data: function(value, state)
2753
	{
2754
		var need_data = false;
2755
2756
		// Determine if we're showing multiple owners seperate or consolidated
2757
		var seperate_owners = false;
2758
		var last_owner = value.length ? value[0].owner || 0 : 0;
2759
		for(var i = 0; i < value.length && !seperate_owners; i++)
2760
		{
2761
			seperate_owners = seperate_owners || (last_owner !== value[i].owner);
2762
		}
2763
2764
		for(var i = 0; i < value.length; i++)
2765
		{
2766
			var t = new Date(value[i].start_date);
2767
			var end = new Date(value[i].end_date);
2768
			do
2769
			{
2770
				// Cache is by date (and owner, if seperate)
2771
				var date = t.getUTCFullYear() + sprintf('%02d',t.getUTCMonth()+1) + sprintf('%02d',t.getUTCDate());
2772
				var cache_id = app.classes.calendar._daywise_cache_id(date, seperate_owners && value[i].owner ? value[i].owner : state.owner||false);
2773
2774
				if(egw.dataHasUID(cache_id))
2775
				{
2776
					var c = egw.dataGetUIDdata(cache_id);
2777
					if(c.data && c.data !== null)
2778
					{
2779
						// There is data, pass it along now
2780
						value[i][date] = [];
2781
						for(var j = 0; j < c.data.length; j++)
2782
						{
2783
							if(egw.dataHasUID('calendar::'+c.data[j]))
2784
							{
2785
								value[i][date].push(egw.dataGetUIDdata('calendar::'+c.data[j]).data);
2786
							}
2787
							else
2788
							{
2789
								need_data = true;
2790
							}
2791
						}
2792
					}
2793
					else
2794
					{
2795
						need_data = true;
2796
						// Assume it's empty, if there is data it will be filled later
2797
						egw.dataStoreUID(cache_id, []);
2798
					}
2799
				}
2800
				else
2801
				{
2802
					need_data = true;
2803
					// Assume it's empty, if there is data it will be filled later
2804
					egw.dataStoreUID(cache_id, []);
2805
				}
2806
				t.setUTCDate(t.getUTCDate() + 1);
2807
			}
2808
			while(t < end);
2809
2810
			// Some data is missing for the current owner, go get it
2811
			if(need_data && seperate_owners)
2812
			{
2813
				this._fetch_data(
2814
					jQuery.extend({}, state, {owner: value[i].owner}),
2815
					this.sidebox_et2 ? null : this.et2.getInstanceManager()
2816
				);
2817
				need_data = false;
2818
			}
2819
		}
2820
2821
		// Some data was missing, go get it
2822
		if(need_data && !seperate_owners)
2823
		{
2824
			this._fetch_data(
2825
				state,
2826
				this.sidebox_et2 ? null : this.et2.getInstanceManager()
2827
			);
2828
		}
2829
2830
		return need_data;
2831
	},
2832
2833
	/**
2834
	 * Use the egw.data system to get data from the calendar list for the
2835
	 * selected time span.
2836
	 *
2837
	 * As long as the other filters are the same (category, owner, status) we
2838
	 * cache the data.
2839
	 *
2840
	 * @param {Object} state
2841
	 * @param {etemplate2} [instance] If the full calendar app isn't loaded
2842
	 *	(home app), pass a different instance to use it to get the data
2843
	 * @param {number} [start] Result offset.  Internal use only
2844
	 */
2845
	_fetch_data: function(state, instance, start)
2846
	{
2847
		if(!this.sidebox_et2 && !instance)
2848
		{
2849
			return;
2850
		}
2851
2852
		if(typeof start === 'undefined')
2853
		{
2854
			start = 0;
2855
		}
2856
2857
		// Category needs to be false if empty, not an empty array or string
2858
		var cat_id = state.cat_id ? state.cat_id : false;
2859
		if(cat_id && typeof cat_id.join != 'undefined')
2860
		{
2861
			if(cat_id.join('') == '') cat_id = false;
2862
		}
2863
		// Make sure cat_id reaches to server in array format
2864
		if (cat_id && typeof cat_id == 'string' && cat_id != "0") cat_id = cat_id.split(',');
2865
2866
		var query = jQuery.extend({}, {
2867
			get_rows: 'calendar.calendar_uilist.get_rows',
2868
			row_id:'row_id',
2869
			startdate:state.first ||  state.date,
2870
			enddate:state.last,
2871
			// Participant must be an array or it won't work
2872
			col_filter: {participant: (typeof state.owner == 'string' || typeof state.owner == 'number' ? [state.owner] : state.owner)},
2873
			filter:'custom', // Must be custom to get start & end dates
2874
			status_filter: state.status_filter,
2875
			cat_id: cat_id,
2876
			csv_export: false
2877
		});
2878
		// Show ajax loader
2879
		if(typeof framework !== 'undefined')
2880
		{
2881
			framework.applications.calendar.sidemenuEntry.showAjaxLoader();
2882
		}
2883
2884
		if(state.view === 'planner' && state.sortby === 'user')
2885
		{
2886
			query.order = 'participants';
2887
		}
2888
		else if (state.view === 'planner' && state.sortby === 'category')
2889
		{
2890
			query.order = 'categories';
2891
		}
2892
2893
		// Already in progress?
2894
		var query_string = JSON.stringify(query);
2895
		if(this._queries_in_progress.indexOf(query_string) != -1)
2896
		{
2897
			return;
2898
		}
2899
		this._queries_in_progress.push(query_string);
2900
2901
		this.egw.dataFetch(
2902
			instance ? instance.etemplate_exec_id :
2903
				this.sidebox_et2.getInstanceManager().etemplate_exec_id,
2904
			{start: start, num_rows:400},
2905
			query,
2906
			this.id,
2907
			function calendar_handleResponse(data) {
2908
				var idx = this._queries_in_progress.indexOf(query_string);
2909
				if(idx >= 0)
2910
				{
2911
					this._queries_in_progress.splice(idx,1);
2912
				}
2913
				//console.log(data);
2914
2915
				// Look for any updated select options
2916
				if(data.rows && data.rows.sel_options && this.sidebox_et2)
2917
				{
2918
					for(var field in data.rows.sel_options)
2919
					{
2920
						var widget = this.sidebox_et2.getWidgetById(field);
2921
						if(widget && widget.set_select_options)
2922
						{
2923
							// Merge in new, update label of existing
2924
							for(var i in data.rows.sel_options[field])
2925
							{
2926
								var found = false;
2927
								var option = data.rows.sel_options[field][i];
2928
								for(var j in widget.options.select_options)
2929
								{
2930
									if(option.value == widget.options.select_options[j].value)
2931
									{
2932
										widget.options.select_options[j].label = option.label;
2933
										found = true;
2934
										break;
2935
									}
2936
								}
2937
								if(!found)
2938
								{
2939
									if(!widget.options.select_options.push)
2940
									{
2941
										widget.options.select_options = [];
2942
									}
2943
									widget.options.select_options.push(option);
2944
								}
2945
							}
2946
							var in_progress = app.calendar.state_update_in_progress;
2947
							app.calendar.state_update_in_progress = true;
2948
							widget.set_select_options(widget.options.select_options);
2949
							widget.set_value(widget.getValue());
2950
2951
							// If updating owner, update listview participants as well
2952
							// This lets us _add_ to the options, normal nm behaviour will replace.
2953
							if(field == 'owner')
2954
							{
2955
								try {
2956
									var participant = app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm').getWidgetById('participant');
2957
									if(participant)
2958
									{
2959
										participant.options.select_options = widget.options.select_options;
2960
										participant.set_select_options(widget.options.select_options);
2961
									}
2962
								} catch(e) {debugger;}
0 ignored issues
show
Debugging Code introduced by
debugger looks like debug code. Are you sure you do not want to remove it?
Loading history...
2963
							}
2964
2965
							app.calendar.state_update_in_progress = in_progress;
2966
						}
2967
					}
2968
				}
2969
2970
				if(data.order && data.total)
2971
				{
2972
					this._update_events(state, data.order);
2973
				}
2974
2975
				// More rows?
2976
				if(data.order.length + start < data.total)
2977
				{
2978
					// Wait a bit, let UI do something.
2979
					window.setTimeout( function() {
2980
						app.calendar._fetch_data(state, instance, start + data.order.length);
2981
					}, 100);
2982
				}
2983
				// Hide AJAX loader
2984
				else if(typeof framework !== 'undefined')
2985
				{
2986
					framework.applications.calendar.sidemenuEntry.hideAjaxLoader();
2987
					egw.loading_prompt('calendar',false)
2988
2989
				}
2990
			}, this,null
2991
		);
2992
	},
2993
2994
	/**
2995
	 * We have a list of calendar UIDs of events that need updating.
2996
	 *
2997
	 * The event data should already be in the egw.data cache, we just need to
2998
	 * figure out where they need to go, and update the needed parent objects.
2999
	 *
3000
	 * Already existing events will have already been updated by egw.data
3001
	 * callbacks.
3002
	 *
3003
	 * @param {Object} state Current state for update, used to determine what to update
3004
	 * @param data
3005
	 */
3006
	_update_events: function(state, data) {
3007
		var updated_days = {};
3008
3009
		// Events can span for longer than we are showing
3010
		var first = new Date(state.first);
3011
		var last = new Date(state.last);
3012
		var bounds = {
3013
			first: ''+first.getUTCFullYear() + sprintf('%02d',first.getUTCMonth()+1) + sprintf('%02d',first.getUTCDate()),
3014
			last: ''+last.getUTCFullYear() + sprintf('%02d',last.getUTCMonth()+1) + sprintf('%02d',last.getUTCDate())
3015
		};
3016
		// Seperate owners, or consolidated?
3017
		var multiple_owner = typeof state.owner != 'string' &&
3018
			state.owner.length > 1 &&
3019
			(state.view == 'day' && state.owner.length < parseInt(this.egw.preference('day_consolidate','calendar')) ||
3020
			['week','day4'].indexOf(state.view) !== -1 && state.owner.length < parseInt(this.egw.preference('week_consolidate','calendar')));
3021
3022
3023
		for(var i = 0; i < data.length; i++)
3024
		{
3025
			var record = this.egw.dataGetUIDdata(data[i]);
3026
			if(record && record.data)
3027
			{
3028
				if(typeof updated_days[record.data.date] === 'undefined')
3029
				{
3030
					// Check to make sure it's in range first, record.data.date is start date
3031
					// and could be before our start
3032
					if(record.data.date >= bounds.first && record.data.date <= bounds.last)
3033
					{
3034
						updated_days[record.data.date] = [];
3035
					}
3036
				}
3037
				if(typeof updated_days[record.data.date] != 'undefined')
3038
				{
3039
					// Copy, to avoid unwanted changes by reference
3040
					updated_days[record.data.date].push(record.data.row_id);
3041
				}
3042
3043
				// Check for multi-day events listed once
3044
				// Date must stay a string or we might cause problems with nextmatch
3045
				var dates = {
3046
					start: typeof record.data.start === 'string' ? record.data.start : record.data.start.toJSON(),
3047
					end: typeof record.data.end === 'string' ? record.data.end : record.data.end.toJSON()
3048
				};
3049
				if(dates.start.substr(0,10) !== dates.end.substr(0,10) && 
3050
						// Avoid events ending at midnight having a 0 length event the next day
3051
						dates.end.substr(11,8) !== '00:00:00')
3052
				{
3053
					var end = new Date(Math.min(new Date(record.data.end), new Date(state.last)));
3054
					end.setUTCHours(23);
3055
					end.setUTCMinutes(59);
3056
					end.setUTCSeconds(59);
3057
					var t = new Date(Math.max(new Date(record.data.start), new Date(state.first)));
3058
3059
					do
3060
					{
3061
						var expanded_date = ''+t.getUTCFullYear() + sprintf('%02d',t.getUTCMonth()+1) + sprintf('%02d',t.getUTCDate());
3062
						if(typeof(updated_days[expanded_date]) === 'undefined')
3063
						{
3064
							// Check to make sure it's in range first, expanded_date could be after our end
3065
							if(expanded_date >= bounds.first && expanded_date <= bounds.last)
3066
							{
3067
								updated_days[expanded_date] = [];
3068
							}
3069
						}
3070
						if(record.data.date !== expanded_date && typeof updated_days[expanded_date] !== 'undefined')
3071
						{
3072
							// Copy, to avoid unwanted changes by reference
3073
							updated_days[expanded_date].push(record.data.row_id);
3074
						}
3075
						t.setUTCDate(t.getUTCDate() + 1);
3076
					}
3077
					while(end >= t)
3078
				}
3079
			}
3080
		}
3081
3082
		// Now we know which days changed, so we pass it on
3083
		for(var day in updated_days)
3084
		{
3085
			// Might be split by user, so we have to check that too
3086
			for(var i = 0; i < (typeof state.owner == 'object' ? state.owner.length : 1); i++)
3087
			{
3088
				var owner = multiple_owner ? state.owner[i] : state.owner;
3089
				var cache_id = app.classes.calendar._daywise_cache_id(day, owner);
3090
				if(egw.dataHasUID(cache_id))
3091
				{
3092
					// Don't lose any existing data, just append
3093
					var c = egw.dataGetUIDdata(cache_id);
3094
					if(c.data && c.data !== null)
3095
					{
3096
						// Avoid duplicates
3097
						var data = c.data.concat(updated_days[day]).filter(function(value, index, self) {
3098
							return self.indexOf(value) === index;
3099
						});
3100
						this.egw.dataStoreUID(cache_id,data);
3101
					}
3102
				}
3103
				else
3104
				{
3105
					this.egw.dataStoreUID(cache_id, updated_days[day]);
3106
				}
3107
				if(!multiple_owner) break;
3108
			}
3109
		}
3110
3111
		egw.loading_prompt(this.appname,false);
3112
	},
3113
3114
	/**
3115
	 * Some handy date calculations
3116
	 * All take either a Date object or full date with timestamp (Z)
3117
	 */
3118
	date: {
3119
		toString: function(date)
3120
		{
3121
			// Ensure consistent formatting using UTC, avoids problems with comparison
3122
			// and timezones
3123
			if(typeof date === 'string') date = new Date(date);
3124
			return date.getUTCFullYear() +'-'+
3125
				sprintf("%02d",date.getUTCMonth()+1) + '-'+
3126
				sprintf("%02d",date.getUTCDate()) + 'T'+
3127
				sprintf("%02d",date.getUTCHours()) + ':'+
3128
				sprintf("%02d",date.getUTCMinutes()) + ':'+
3129
				sprintf("%02d",date.getUTCSeconds()) + 'Z';
3130
		},
3131
3132
		/**
3133
		* Formats one or two dates (range) as long date (full monthname), optionaly with a time
3134
		*
3135
		* Take care of any timezone issues before you pass the dates in.
3136
		*
3137
		* @param {Date} first first date
3138
		* @param {Date} last =0 last date for range, or false for a single date
3139
		* @param {boolean} display_time =false should a time be displayed too
3140
		* @param {boolean} display_day =false should a day-name prefix the date, eg. monday June 20, 2006
3141
		* @return string with formatted date
3142
		*/
3143
		long_date: function(first, last, display_time, display_day)
3144
		{
3145
			if(!first) return '';
3146
			if(typeof first === 'string')
3147
			{
3148
				first = new Date(first);
3149
			}
3150
			var first_format = new Date(first.valueOf() + first.getTimezoneOffset() * 60 * 1000);
3151
3152
			if(typeof last == 'string' && last)
3153
			{
3154
				last = new Date(last);
3155
			}
3156
			if(!last || typeof last !== 'object')
3157
			{
3158
				 last = false;
3159
			}
3160
			if(last)
3161
			{
3162
				var last_format = new Date(last.valueOf() + last.getTimezoneOffset() * 60 * 1000);
3163
			}
3164
3165
			if(!display_time) display_time = false;
3166
			if(!display_day) display_day = false;
3167
3168
			var range = '';
3169
3170
			var datefmt = egw.preference('dateformat');
3171
			var timefmt = egw.preference('timeformat') === '12' ? 'h:i a' : 'H:i';
3172
3173
			var month_before_day = datefmt[0].toLowerCase() == 'm' ||
3174
				datefmt[2].toLowerCase() == 'm' && datefmt[4] == 'd';
3175
3176
			if (display_day)
3177
			{
3178
				range = jQuery.datepicker.formatDate('DD',first_format)+(datefmt[0] != 'd' ? ' ' : ', ');
3179
			}
3180
			for (var i = 0; i < 5; i += 2)
3181
			{
3182
				 switch(datefmt[i])
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
3183
				 {
3184
					 case 'd':
3185
						 range += first.getUTCDate()+ (datefmt[1] == '.' ? '.' : '');
3186
						 if (last && (first.getUTCMonth() != last.getUTCMonth() || first.getUTCFullYear() != last.getUTCFullYear()))
3187
						 {
3188
							 if (!month_before_day)
3189
							 {
3190
								 range += jQuery.datepicker.formatDate('MM',first_format);
3191
							 }
3192
							 if (first.getFullYear() != last.getFullYear() && datefmt[0] != 'Y')
3193
							 {
3194
								 range += (datefmt[0] != 'd' ? ', ' : ' ') + first.getFullYear();
3195
							 }
3196
							 if (display_time)
3197
							 {
3198
								 range += ' '+jQuery.datepicker.formatDate(dateTimeFormat(timefmt),first_format);
3199
							 }
3200
							 if (!last)
3201
							 {
3202
								 return range;
3203
							 }
3204
							 range += ' - ';
3205
3206
							 if (first.getFullYear() != last.getFullYear() && datefmt[0] == 'Y')
3207
							 {
3208
								 range += last.getUTCFullYear() + ', ';
3209
							 }
3210
3211
							 if (month_before_day)
3212
							 {
3213
								 range += jQuery.datepicker.formatDate('MM',last_format);
0 ignored issues
show
Bug introduced by
The variable last_format does not seem to be initialized in case last on line 3160 is false. Are you sure the function formatDate handles undefined variables?
Loading history...
3214
							 }
3215
						 }
3216
						 else
3217
						 {
3218
							 if (display_time)
3219
							 {
3220
								 range += ' '+jQuery.datepicker.formatDate(dateTimeFormat(timefmt),last_format);
3221
							 }
3222
							 if(last)
3223
							 {
3224
								 range += ' - ';
3225
							 }
3226
						 }
3227
						 if(last)
3228
						 {
3229
							 range += ' ' + last.getUTCDate() + (datefmt[1] == '.' ? '.' : '');
3230
						 }
3231
						 break;
3232
					 case 'm':
3233
					 case 'M':
3234
						 range += ' '+jQuery.datepicker.formatDate('MM',month_before_day ? first_format : last_format) + ' ';
3235
						 break;
3236
					 case 'Y':
3237
						 if (datefmt[0] != 'm')
3238
						 {
3239
							 range += ' ' + (datefmt[0] == 'Y' ? first.getUTCFullYear()+(datefmt[2] == 'd' ? ', ' : ' ') : last.getUTCFullYear()+' ');
3240
						 }
3241
						 break;
3242
				 }
3243
			}
3244
			if (display_time && last)
3245
			{
3246
				 range += ' '+jQuery.datepicker.formatDate(dateTimeFormat(timefmt),last_format);
3247
			}
3248
			if (datefmt[4] == 'Y' && datefmt[0] == 'm')
3249
			{
3250
				 range += ', ' + last.getUTCFullYear();
3251
			}
3252
			return range;
3253
		},
3254
		/**
3255
		* Calculate iso8601 week-number, which is defined for Monday as first day of week only
3256
		*
3257
		* We adjust the day, if user prefs want a different week-start-day
3258
		*
3259
		* @param {string|Date} _date
3260
		* @return string
3261
		*/
3262
		week_number: function(_date)
3263
		{
3264
			var d = new Date(_date);
3265
			var day = d.getUTCDay();
3266
3267
3268
			// if week does not start Monday and date is Sunday --> add one day
3269
			if (egw.preference('weekdaystarts','calendar') != 'Monday' && !day)
3270
			{
3271
				d.setUTCDate(d.getUTCDate() + 1);
3272
			}
3273
			// if week does start Saturday and $time is Saturday --> add two days
3274
			else if (egw.preference('weekdaystarts','calendar') == 'Saturday' && day == 6)
3275
			{
3276
				d.setUTCDate(d.getUTCDate() + 2);
3277
			}
3278
3279
			return jQuery.datepicker.iso8601Week(new Date(d.valueOf() + d.getTimezoneOffset() * 60 * 1000));
3280
		},
3281
		start_of_week: function(date)
3282
		{
3283
			var d = new Date(date);
3284
			var day = d.getUTCDay();
3285
			var diff = 0;
3286
			switch(egw.preference('weekdaystarts','calendar'))
3287
			{
3288
				case 'Saturday':
3289
					diff = day === 6 ? 0 : day === 0 ? -1 : -(day + 1);
3290
					break;
3291
				case 'Monday':
3292
					diff = day === 0 ? -6 : 1-day;
3293
					break;
3294
				case 'Sunday':
3295
				default:
3296
					diff = -day;
3297
			}
3298
			d.setUTCDate(d.getUTCDate() + diff);
3299
			return d;
3300
		},
3301
		end_of_week: function(date)
3302
		{
3303
			var d = app.calendar.date.start_of_week(date);
3304
			d.setUTCDate(d.getUTCDate() + 6);
3305
			return d;
3306
		}
3307
	},
3308
3309
	/**
3310
	 * The sidebox filters use some non-standard and not-exposed options.  They
3311
	 * are set up here.
3312
	 *
3313
	 */
3314
	_setup_sidebox_filters: function()
3315
	{
3316
		// Further date customizations
3317
		var date_widget = this.sidebox_et2.getWidgetById('date');
3318
		if(date_widget)
3319
		{
3320
			// Dynamic resize of sidebox calendar to fill sidebox
3321
			var preferred_width = jQuery('#calendar-sidebox_date .ui-datepicker-inline').outerWidth();
3322
			var font_ratio = 12 / parseFloat(jQuery('#calendar-sidebox_date .ui-datepicker-inline').css('font-size'));
3323
			var calendar_resize = function() {
3324
				try {
3325
					var percent = 1+((jQuery(date_widget.getDOMNode()).width() - preferred_width) / preferred_width);
3326
					percent *= font_ratio;
3327
					jQuery('#calendar-sidebox_date .ui-datepicker-inline')
3328
						.css('font-size',(percent*100)+'%');
3329
3330
					// Position go and today
3331
					var buttons = jQuery('#calendar-sidebox_date .ui-datepicker-header a span');
3332
					if(today.length && go_button.length)
3333
					{
3334
						go_button.position({my: 'left+8px center', at: 'right center-1',of: jQuery('#calendar-sidebox_date .ui-datepicker-year')});
3335
						today.css({
3336
							'left': (buttons.first().offset().left + buttons.last().offset().left)/2 - Math.ceil(today.outerWidth(true)/2),
3337
							'top': go_button.css('top')
3338
						});
3339
						buttons.position({my: 'center', at: 'center', of: go_button})
3340
							.css('left', '');
3341
					}
3342
				} catch (e){
3343
					// Resize didn't work
3344
				}
3345
			};
3346
3347
			var datepicker = date_widget.input_date.datepicker("option", {
3348
				showButtonPanel:	false,
3349
				onChangeMonthYear: function(year, month, inst)
3350
				{
3351
					// Update month button label
3352
					var go_button = date_widget.getRoot().getWidgetById('header_go');
3353
					if(go_button)
3354
					{
3355
						var temp_date = new Date(year, month-1, 1,0,0,0);
3356
						//temp_date.setUTCMinutes(temp_date.getUTCMinutes() + temp_date.getTimezoneOffset());
3357
						go_button.btn.attr('title',egw.lang(date('F',temp_date)));
3358
3359
						// Store current _displayed_ date in date button for clicking
3360
						temp_date.setUTCMinutes(temp_date.getUTCMinutes() - temp_date.getTimezoneOffset());
3361
						go_button.btn.attr('data-date', temp_date.toJSON());
3362
					}
3363
					window.setTimeout(calendar_resize,0);
3364
				},
3365
				// Mark holidays
3366
				beforeShowDay: function (date)
3367
				{
3368
					var holidays = et2_calendar_view.get_holidays({day_class_holiday: function() {}}, date.getFullYear());
3369
					var day_holidays = holidays[''+date.getFullYear() +
3370
						sprintf("%02d",date.getMonth()+1) +
3371
						sprintf("%02d",date.getDate())];
3372
					var css_class = '';
3373
					var tooltip = '';
3374
					if(typeof day_holidays !== 'undefined' && day_holidays.length)
3375
					{
3376
						for(var i = 0; i < day_holidays.length; i++)
3377
						{
3378
							if (typeof day_holidays[i]['birthyear'] !== 'undefined')
3379
							{
3380
								css_class +='calendar_calBirthday ';
3381
							}
3382
							else
3383
							{
3384
								css_class += 'calendar_calHoliday ';
3385
							}
3386
							tooltip += day_holidays[i]['name'] + "\n";
3387
						}
3388
					}
3389
					return [true, css_class, tooltip];
3390
				}
3391
			});
3392
3393
			// Clickable week numbers
3394
			date_widget.input_date.on('mouseenter','.ui-datepicker-week-col', function() {
3395
					jQuery(this).siblings().find('a').addClass('ui-state-hover');
3396
				})
3397
				.on('mouseleave','.ui-datepicker-week-col', function() {
3398
					jQuery(this).siblings().find('a').removeClass('ui-state-hover');
3399
				})
3400
				.on('click', '.ui-datepicker-week-col', function() {
3401
					var view = app.calendar.state.view;
3402
					var days = app.calendar.state.days;
3403
3404
					// Avoid a full state update, we just want the calendar to update
3405
					// Directly update to avoid change event from the sidebox calendar
3406
					var date = new Date(this.nextSibling.dataset.year,this.nextSibling.dataset.month,this.nextSibling.firstChild.textContent,0,0,0);
3407
					date.setUTCMinutes(date.getUTCMinutes() - date.getTimezoneOffset());
3408
					date = app.calendar.date.toString(date);
3409
3410
					// Set to week view, if in one of the views where we change view
3411
					if(app.calendar.sidebox_changes_views.indexOf(view) >= 0)
3412
					{
3413
						app.calendar.update_state({view: 'week', date: date, days: days});
3414
					}
3415
					else if (view == 'planner')
3416
					{
3417
						// Clicked a week, show just a week
3418
						app.calendar.update_state({date: date, planner_view: 'week'});
3419
					}
3420
					else if (view == 'listview')
3421
					{
3422
						app.calendar.update_state({
3423
							date: date,
3424
							end_date: app.calendar.date.toString(app.classes.calendar.views.week.end_date({date:date})),
3425
							filter: 'week'
3426
						});
3427
					}
3428
					else
3429
					{
3430
						app.calendar.update_state({date: date});
3431
					}
3432
				});
3433
3434
3435
			// Set today button
3436
			var today = jQuery('#calendar-sidebox_header_today');
3437
			today.attr('title',egw.lang('today'));
3438
3439
			// Set go button
3440
			var go_button = date_widget.getRoot().getWidgetById('header_go');
3441
			if(go_button && go_button.btn)
3442
			{
3443
				go_button = go_button.btn;
3444
				var temp_date = new Date(date_widget.get_value());
3445
				temp_date.setUTCDate(1);
3446
				temp_date.setUTCMinutes(temp_date.getUTCMinutes() + temp_date.getTimezoneOffset());
3447
3448
				go_button.attr('title', egw.lang(date('F',temp_date)));
3449
				// Store current _displayed_ date in date button for clicking
3450
				temp_date.setUTCMinutes(temp_date.getUTCMinutes() - temp_date.getTimezoneOffset());
3451
				go_button.attr('data-date', temp_date.toJSON());
3452
3453
			}
3454
		}
3455
3456
		jQuery(window).on('resize.calendar'+date_widget.dom_id,calendar_resize).trigger('resize');
0 ignored issues
show
Bug introduced by
The variable calendar_resize does not seem to be initialized in case date_widget on line 3318 is false. Are you sure the function on handles undefined variables?
Loading history...
3457
3458
		// Avoid wrapping owner icons if user has group + search
3459
		var button = jQuery('#calendar-sidebox_owner ~ span.et2_clickable');
3460
		if(button.length == 1)
3461
		{
3462
			button.parent().css('margin-right',button.outerWidth(true)+2);
3463
			button.parent().parent().css('white-space','nowrap');
3464
		}
3465
		jQuery(window).on('resize.calendar-owner', function() {
3466
			var preferred_width = jQuery('#calendar-et2_target').children().first().outerWidth()||0;
3467
			if(app.calendar && app.calendar.sidebox_et2)
3468
			{
3469
				var owner = app.calendar.sidebox_et2.getWidgetById('owner');
3470
				if(preferred_width && owner.input.hasClass("chzn-done"))
3471
				{
3472
					owner.input.next().css('width',preferred_width);
3473
				}
3474
			}
3475
		});
3476
	},
3477
3478
	/**
3479
	 * Record view templates so we can quickly switch between them.
3480
	 *
3481
	 * @param {etemplate2} _et2 etemplate2 template that was just loaded
3482
	 * @param {String} _name Name of the template
3483
	 */
3484
	_et2_view_init: function(_et2, _name)
3485
	{
3486
		var hidden = typeof this.state.view !== 'undefined';
3487
		var all_loaded = this.sidebox_et2 !== null;
3488
3489
		// Avoid home portlets using our templates, and get them right
3490
		if(_et2.uniqueId.indexOf('portlet') === 0) return;
3491
3492
		// Flag to make sure we don't hide non-view templates
3493
		var view_et2 = false;
3494
3495
		for(var view in app.classes.calendar.views)
3496
		{
3497
			var index = app.classes.calendar.views[view].etemplates.indexOf(_name);
3498
			if(index > -1)
3499
			{
3500
				view_et2 = true;
3501
				app.classes.calendar.views[view].etemplates[index] = _et2;
3502
				// If a template disappears, we want to release it
3503
				jQuery(_et2.DOMContainer).one('clear',jQuery.proxy(function() {
3504
					this.view.etemplates[this.index] = _name;
3505
				},jQuery.extend({},{view: app.classes.calendar.views[view], index: ""+index, name: _name})));
3506
3507
				if(this.state.view === view)
3508
				{
3509
					hidden = false;
3510
				}
3511
			}
3512
			app.classes.calendar.views[view].etemplates.forEach(function(et) {all_loaded = all_loaded && typeof et !== 'string';});
3513
		}
3514
3515
		// Add some extras to the nextmatch so it can keep the dates in sync with
3516
		// those in the sidebox calendar.  Care must be taken to not trigger any
3517
		// sort of refresh or update, as that may resulte in infinite loops so these
3518
		// are only used for the 'week' and 'month' filters, and we just update the
3519
		// date range
3520
		if(_name == 'calendar.list')
3521
		{
3522
			var nm = _et2.widgetContainer.getWidgetById('nm');
3523
			if(nm)
3524
			{
3525
				// Avoid unwanted refresh immediately after load
3526
				nm.controller._grid.doInvalidate = false;
3527
3528
				// Preserve pre-set search
3529
				if(nm.activeFilters.search)
3530
				{
3531
					this.state.keywords = nm.activeFilters.search;
3532
				}
3533
				// Bind to keep search up to date
3534
				jQuery(nm.getWidgetById('search').getDOMNode()).on('change', function() {
3535
					app.calendar.state.search = jQuery('input',this).val();
3536
				});
3537
				nm.set_startdate = jQuery.proxy(function(date) {
3538
					this.state.first = this.date.toString(new Date(date));
3539
				},this);
3540
				nm.set_enddate = jQuery.proxy(function(date) {
3541
					this.state.last = this.date.toString(new Date(date));
3542
				},this);
3543
			}
3544
		}
3545
3546
		// Start hidden, except for current view
3547
		if(view_et2)
3548
		{
3549
			if(hidden)
3550
			{
3551
				jQuery(_et2.DOMContainer).hide();
3552
			}
3553
		}
3554
		else
3555
		{
3556
			var app_name = _name.split('.')[0];
3557
			if(app_name && app_name != 'calendar' && egw.app(app_name))
3558
			{
3559
				// A template from another application?  Keep it up to date as state changes
3560
				this.sidebox_hooked_templates.push(_et2.widgetContainer);
3561
				// If it leaves (or reloads) remove it
3562
				jQuery(_et2.DOMContainer).one('clear',jQuery.proxy(function() {
3563
					if(app.calendar)
3564
					{
3565
						app.calendar.sidebox_hooked_templates.splice(this,1,0);
3566
					}
3567
				},this.sidebox_hooked_templates.length -1));
3568
			}
3569
		}
3570
		if(all_loaded)
3571
		{
3572
			jQuery(window).trigger('resize');
3573
			this.setState({state:this.state});
3574
3575
			// Hide loader after 1 second as a fallback, it will also be hidden
3576
			// after loading is complete.
3577
			window.setTimeout(jQuery.proxy(function() {
3578
				egw.loading_prompt(this.appname,false);
3579
			}, this),1000);
3580
3581
			// Start calendar-wide autorefresh timer to include more than just nm
3582
			this._set_autorefresh();
3583
		}
3584
	},
3585
3586
	/**
3587
	 * Set a refresh timer that works for the current view.
3588
	 * The nextmatch goes into an infinite loop if we let it autorefresh while
3589
	 * hidden.
3590
	 */
3591
	_set_autorefresh: function() {
3592
		// Listview not loaded
3593
		if(typeof app.classes.calendar.views.listview.etemplates[0] == 'string') return;
3594
3595
		var nm = app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm');
3596
		// nextmatch missing
3597
		if(!nm) return;
3598
3599
		var refresh_preference = "nextmatch-" + nm.options.settings.columnselection_pref + "-autorefresh";
3600
		var time = this.egw.preference(refresh_preference, 'calendar');
3601
3602
		if(this.state.view == 'listview' && time)
3603
		{
3604
			nm._set_autorefresh(time);
3605
			return;
3606
		}
3607
		else
3608
		{
3609
			window.clearInterval(nm._autorefresh_timer);
3610
		}
3611
		var self = this;
3612
		var refresh = function() {
3613
			// Deleted events are not coming properly, so clear it all
3614
			self._clear_cache();
3615
			// Force redraw to current state
3616
			self.setState({state: self.state});
3617
3618
			// This is a fast update, but misses deleted events
3619
			//app.calendar._fetch_data(app.calendar.state);
3620
		};
3621
3622
		// Start / update timer
3623
		if (this._autorefresh_timer)
3624
		{
3625
			window.clearInterval(this._autorefresh_timer);
3626
			this._autorefresh_timer = null;
3627
		}
3628
		if(time > 0)
3629
		{
3630
			this._autorefresh_timer = setInterval(jQuery.proxy(refresh, this), time * 1000);
3631
		}
3632
3633
		// Bind to tab show/hide events, so that we don't bother refreshing in the background
3634
		jQuery(nm.getInstanceManager().DOMContainer.parentNode).on('hide.calendar', jQuery.proxy(function(e) {
3635
			// Stop
3636
			window.clearInterval(this._autorefresh_timer);
3637
			jQuery(e.target).off(e);
3638
3639
			if(!time) return;
3640
3641
			// If the autorefresh time is up, bind once to trigger a refresh
3642
			// (if needed) when tab is activated again
3643
			this._autorefresh_timer = setTimeout(jQuery.proxy(function() {
3644
				// Check in case it was stopped / destroyed since
3645
				if(!this._autorefresh_timer) return;
3646
3647
				jQuery(nm.getInstanceManager().DOMContainer.parentNode).one('show.calendar',
3648
					// Important to use anonymous function instead of just 'this.refresh' because
3649
					// of the parameters passed
3650
					jQuery.proxy(function() {refresh();},this)
3651
				);
3652
			},this), time*1000);
3653
		},this));
3654
		jQuery(nm.getInstanceManager().DOMContainer.parentNode).on('show.calendar', jQuery.proxy(function(e) {
3655
			// Start normal autorefresh timer again
3656
			this._set_autorefresh(this.egw.preference(refresh_preference, 'calendar'));
3657
			jQuery(e.target).off(e);
3658
		},this));
3659
	},
3660
3661
	/**
3662
	 * Super class for the different views.
3663
	 *
3664
	 * Each separate view overrides what it needs
3665
	 */
3666
	View: {
3667
		// List of etemplates to show for this view
3668
		etemplates: ['calendar.view'],
3669
3670
		/**
3671
		 * Translated label for header
3672
		 * @param {Object} state
3673
		 * @returns {string}
3674
		 */
3675
		header: function(state) {
3676
			var formatDate = new Date(state.date);
3677
			formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
3678
			return app.calendar.View._owner(state) + date(egw.preference('dateformat'),formatDate);
3679
		},
3680
3681
		/**
3682
		 * If one owner, get the owner text
3683
		 *
3684
		 * @param {object} state
3685
		 */
3686
		_owner: function(state) {
3687
			var owner = '';
3688
			if(state.owner.length && state.owner.length == 1 && app.calendar.sidebox_et2)
3689
			{
3690
				var own = app.calendar.sidebox_et2.getWidgetById('owner').getDOMNode();
3691
				if(own.selectedIndex >= 0)
3692
				{
3693
					owner = own.options[own.selectedIndex].innerHTML + ": ";
3694
				}
3695
			}
3696
			return owner;
3697
		},
3698
3699
		/**
3700
		 * Get the start date for this view
3701
		 * @param {Object} state
3702
		 * @returns {Date}
3703
		 */
3704
		start_date: function(state) {
3705
			var d = state.date ? new Date(state.date) : new Date();
3706
			d.setUTCHours(0);
3707
			d.setUTCMinutes(0);
3708
			d.setUTCSeconds(0);
3709
			d.setUTCMilliseconds(0);
3710
			return d;
3711
		},
3712
		/**
3713
		 * Get the end date for this view
3714
		 * @param {Object} state
3715
		 * @returns {Date}
3716
		 */
3717
		end_date: function(state) {
3718
			var d = state.date ? new Date(state.date) : new Date();
3719
			d.setUTCHours(23);
3720
			d.setUTCMinutes(59);
3721
			d.setUTCSeconds(59);
3722
			d.setUTCMilliseconds(0);
3723
			return d;
3724
		},
3725
		/**
3726
		 * Get the owner for this view
3727
		 *
3728
		 * This is always the owner from the given state, we use a function
3729
		 * to trigger setting the widget value.
3730
		 *
3731
		 * @param {number[]|String} state state.owner List of owner IDs, or a comma seperated list
3732
		 * @returns {number[]|String}
3733
		 */
3734
		owner: function(state) {
3735
			return state.owner || 0;
3736
		},
3737
		/**
3738
		 * Should the view show the weekends
3739
		 *
3740
		 * @param {object} state
3741
		 * @returns {boolean} Current preference to show 5 or 7 days in weekview
3742
		 */
3743
		show_weekend: function(state)
3744
		{
3745
			return state.weekend;
3746
		},
3747
		/**
3748
		 * How big or small are the displayed time chunks?
3749
		 *
3750
		 * @param {object} state
3751
		 */
3752
		granularity: function(state) {
3753
			var list = egw.preference('use_time_grid','calendar');
3754
			if(list === 0 || typeof list === 'undefined')
3755
			{
3756
				return parseInt(egw.preference('interval','calendar')) || 30;
3757
			}
3758
			if(typeof list == 'string') list = list.split(',');
3759
			if(!list.indexOf && jQuery.isPlainObject(list))
3760
			{
3761
				list = jQuery.map(list, function(el) { return el; });
3762
			}
3763
			return list.indexOf(state.view) >= 0 ?
3764
				0 :
3765
				parseInt(egw.preference('interval','calendar')) || 30;
3766
		},
3767
		extend: function(sub)
3768
		{
3769
			return jQuery.extend({},this,{_super:this},sub);
3770
		},
3771
		/**
3772
		 * Determines the new date after scrolling.  The default is 1 week.
3773
		 *
3774
		 * @param {number} delta Integer for how many 'ticks' to move, positive for
3775
		 *	forward, negative for backward
3776
		 * @returns {Date}
3777
		 */
3778
		scroll: function(delta)
3779
		{
3780
			var d = new Date(app.calendar.state.date);
3781
			d.setUTCDate(d.getUTCDate() + (7 * delta));
3782
			return d;
3783
		}
3784
	},
3785
3786
	/**
3787
	 * Initialization function in order to set/unset
3788
	 * categories status.
3789
	 *
3790
	 */
3791
	category_report_init: function ()
3792
	{
3793
		var content = this.et2.getArrayMgr('content').data;
3794
		for (var i=1;i<content.grid.length;i++)
3795
		{
3796
			if (content.grid[i] != null) this.category_report_enable({id:i+'', checked:content.grid[i]['enable']});
3797
		}
3798
	},
3799
3800
	/**
3801
	 * Set/unset selected category's row
3802
	 *
3803
	 * @param {type} _widget
3804
	 * @returns {undefined}
3805
	 */
3806
	category_report_enable: function (_widget)
3807
	{
3808
		var widgets = ['[user]','[weekend]','[holidays]','[min_days]'];
3809
		var row_id = _widget.id.match(/\d+/);
3810
		var w = {};
3811
		for (var i=0;i<widgets.length;i++)
3812
		{
3813
			w = this.et2.getWidgetById(row_id+widgets[i]);
3814
			if (w) w.set_readonly(!_widget.checked);
3815
		}
3816
	},
3817
3818
	/**
3819
	 * submit function for report button
3820
	 */
3821
	category_report_submit: function ()
3822
	{
3823
		this.et2._inst.postSubmit();
3824
	},
3825
3826
	/**
3827
	 * Function to enable/disable categories
3828
	 *
3829
	 * @param {object} _widget select all checkbox
3830
	 */
3831
	category_report_selectAll: function (_widget)
3832
	{
3833
		var content = this.et2.getArrayMgr('content').data;
3834
		var checkbox = {};
3835
		var grid_index = typeof content.grid.length !='undefined'? content.grid : Object.keys(content.grid);
3836
		for (var i=1;i< grid_index.length;i++)
3837
		{
3838
			if (content.grid[i] != null)
3839
			{
3840
				checkbox = this.et2.getWidgetById(i+'[enable]');
3841
				if (checkbox)
3842
				{
3843
					checkbox.set_value(_widget.checked);
3844
					this.category_report_enable({id:checkbox.id, checked:checkbox.get_value()});
3845
				}
3846
			}
3847
		}
3848
	}
3849
});}).call(this);
3850
3851
3852
jQuery.extend(app.classes.calendar,{
3853
3854
	/**
3855
	 * This is the data cache prefix for the daywise event index cache
3856
	 * Daywise cache IDs look like: calendar_daywise::20150101 and
3857
	 * contain a list of event IDs for that day (or empty array)
3858
	 */
3859
	DAYWISE_CACHE_ID: 'calendar_daywise',
3860
3861
3862
	/**
3863
	 * Create a cache ID for the daywise cache
3864
	 *
3865
	 * @param {String|Date} date If a string, date should be in Ymd format
3866
	 * @param {String|integer|String[]} owner
3867
	 * @returns {String} Cache ID
3868
	 */
3869
	_daywise_cache_id: function(date, owner)
3870
	{
3871
		if(typeof date === 'object')
3872
		{
3873
			date =  date.getUTCFullYear() + sprintf('%02d',date.getUTCMonth()+1) + sprintf('%02d',date.getUTCDate());
3874
		}
3875
3876
	// If the owner is not set, 0, or the current user, don't bother adding it
3877
		var _owner = (owner && owner.toString() != '0') ? owner.toString() : '';
3878
		if(_owner == egw.user('account_id'))
3879
		{
3880
			_owner = '';
3881
		}
3882
		return app.classes.calendar.DAYWISE_CACHE_ID+'::'+date+(_owner ? '-' + _owner : '');
3883
	},
3884
3885
	/**
3886
	* Etemplates and settings for the different views.  Some (day view)
3887
	* use more than one template, some use the same template as others,
3888
	* most need different handling for their various attributes.
3889
	*
3890
	* Not using the standard Class.extend here because it hides the members,
3891
	* and we want to be able to look inside them.  This is done seperately instead
3892
	* of inside the normal object to allow access to the View object.
3893
	*/
3894
	views: {
3895
		day: app.classes.calendar.prototype.View.extend({
3896
			header: function(state) {
3897
				return app.calendar.View.header.call(this, state);
3898
			},
3899
			etemplates: ['calendar.view','calendar.todo'],
3900
			start_date: function(state) {
3901
				var d = app.calendar.View.start_date.call(this, state);
3902
				state.date = app.calendar.date.toString(d);
3903
				return d;
3904
			},
3905
			show_weekend: function(state) {
3906
				state.days = '1';
3907
				return true;
3908
			},
3909
			scroll: function(delta)
3910
			{
3911
				var d = new Date(app.calendar.state.date);
3912
				d.setUTCDate(d.getUTCDate() + (delta));
3913
				return d;
3914
			}
3915
		}),
3916
		day4: app.classes.calendar.prototype.View.extend({
3917
			header: function(state) {
3918
				return app.calendar.View.header.call(this, state);
3919
			},
3920
			end_date: function(state) {
3921
				var d = app.calendar.View.end_date.call(this,state);
3922
				state.days = '4';
3923
				d.setUTCHours(24*4-1);
3924
				d.setUTCMinutes(59);
3925
				d.setUTCSeconds(59);
3926
				d.setUTCMilliseconds(0);
3927
				return d;
3928
			},
3929
			show_weekend: function(state) {
3930
				state.weekend = 'true';
3931
				return true;
3932
			},
3933
			scroll: function(delta)
3934
			{
3935
				var d = new Date(app.calendar.state.date);
3936
				d.setUTCDate(d.getUTCDate() + (4 * delta));
3937
				return d;
3938
			}
3939
		}),
3940
		week: app.classes.calendar.prototype.View.extend({
3941
			header: function(state) {
3942
				var end_date = state.last;
3943
				if(!app.classes.calendar.views.week.show_weekend(state))
3944
				{
3945
					end_date = new Date(state.last);
3946
					end_date.setUTCDate(end_date.getUTCDate() - 2);
3947
				}
3948
				return app.calendar.View._owner(state) + app.calendar.egw.lang('Week') + ' ' +
3949
					app.calendar.date.week_number(state.first) + ': ' +
3950
					app.calendar.date.long_date(state.first, end_date);
3951
			},
3952
			start_date: function(state) {
3953
				return app.calendar.date.start_of_week(app.calendar.View.start_date.call(this,state));
3954
			},
3955
			end_date: function(state) {
3956
				var d = app.calendar.date.start_of_week(state.date || new Date());
3957
				// Always 7 days, we just turn weekends on or off
3958
				d.setUTCHours(24*7-1);
3959
				d.setUTCMinutes(59);
3960
				d.setUTCSeconds(59);
3961
				d.setUTCMilliseconds(0);
3962
				return d;
3963
			}
3964
		}),
3965
		weekN: app.classes.calendar.prototype.View.extend({
3966
			header: function(state) {
3967
				return  app.calendar.View._owner(state) + app.calendar.egw.lang('Week') + ' ' +
3968
					app.calendar.date.week_number(state.first) + ' - ' +
3969
					app.calendar.date.week_number(state.last) + ': ' +
3970
					app.calendar.date.long_date(state.first, state.last);
3971
			},
3972
			start_date: function(state) {
3973
				return app.calendar.date.start_of_week(app.calendar.View.start_date.call(this,state));
3974
			},
3975
			end_date: function(state) {
3976
				state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview','calendar') || 7);
3977
3978
				var d = app.calendar.date.start_of_week(app.calendar.View.start_date.call(this,state));
3979
				// Always 7 days, we just turn weekends on or off
3980
				d.setUTCHours(24*7*(parseInt(this.egw.preference('multiple_weeks','calendar')) || 3)-1);
3981
				return d;
3982
			}
3983
		}),
3984
		month: app.classes.calendar.prototype.View.extend({
3985
			header: function(state)
3986
			{
3987
				var formatDate = new Date(state.date);
3988
				formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
3989
				return app.calendar.View._owner(state) + app.calendar.egw.lang(date('F',formatDate)) + ' ' + date('Y',formatDate);
3990
			},
3991
			start_date: function(state) {
3992
				var d = app.calendar.View.start_date.call(this,state);
3993
				d.setUTCDate(1);
3994
				return app.calendar.date.start_of_week(d);
3995
			},
3996
			end_date: function(state) {
3997
				var d = app.calendar.View.end_date.call(this,state);
3998
				d = new Date(d.getFullYear(),d.getUTCMonth() + 1, 1,0,-d.getTimezoneOffset(),0);
3999
				d.setUTCSeconds(d.getUTCSeconds()-1);
4000
				return app.calendar.date.end_of_week(d);
4001
			},
4002
			scroll: function(delta)
4003
			{
4004
				var d = new Date(app.calendar.state.date);
4005
				// Set day to 15 so we don't get overflow on short months
4006
				// eg. Aug 31 + 1 month = Sept 31 -> Oct 1
4007
				d.setUTCDate(15);
4008
				d.setUTCMonth(d.getUTCMonth() + delta);
4009
				return d;
4010
			}
4011
		}),
4012
4013
		planner: app.classes.calendar.prototype.View.extend({
4014
			header: function(state) {
4015
				var startDate = new Date(state.first);
4016
				startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);
4017
4018
				var endDate = new Date(state.last);
4019
				endDate = new Date(endDate.valueOf() + endDate.getTimezoneOffset() * 60 * 1000);
4020
				return app.calendar.View._owner(state) + date(egw.preference('dateformat'),startDate) +
4021
					(startDate == endDate ? '' : ' - ' + date(egw.preference('dateformat'),endDate));
4022
			},
4023
			etemplates: ['calendar.planner'],
4024
			group_by: function(state) {
4025
				return state.sortby ? state.sortby : 0;
4026
			},
4027
			// Note: Planner uses the additional value of planner_view to determine
4028
			// the start & end dates using other view's functions
4029
			start_date: function(state) {
4030
				// Start here, in case we can't find anything better
4031
				var d = app.calendar.View.start_date.call(this, state);
4032
4033
				if(state.sortby && state.sortby === 'month')
4034
				{
4035
					d.setUTCDate(1);
4036
				}
4037
				else if (state.planner_view && app.classes.calendar.views[state.planner_view])
4038
				{
4039
					d = app.classes.calendar.views[state.planner_view].start_date.call(this,state);
4040
				}
4041
				else
4042
				{
4043
					d = app.calendar.date.start_of_week(d);
4044
					d.setUTCHours(0);
4045
					d.setUTCMinutes(0);
4046
					d.setUTCSeconds(0);
4047
					d.setUTCMilliseconds(0);
4048
					return d;
4049
				}
4050
				return d;
4051
			},
4052
			end_date: function(state) {
4053
4054
				var d = app.calendar.View.end_date.call(this, state);
4055
				if(state.sortby && state.sortby === 'month')
4056
				{
4057
					d.setUTCDate(0);
4058
					d.setUTCFullYear(d.getUTCFullYear() + 1);
4059
				}
4060
				else if (state.planner_view && app.classes.calendar.views[state.planner_view])
4061
				{
4062
					d = app.classes.calendar.views[state.planner_view].end_date.call(this,state);
4063
				}
4064
				else if (state.days)
4065
				{
4066
					// This one comes from a grid view, but we'll use it
4067
					d.setUTCDate(d.getUTCDate() + parseInt(state.days)-1);
4068
					delete state.days;
4069
				}
4070
				else
4071
				{
4072
					d = app.calendar.date.end_of_week(d);
4073
				}
4074
				return d;
4075
			},
4076
			hide_empty: function(state) {
4077
				var check = state.sortby == 'user' ? ['user','both'] : ['cat','both'];
4078
				return (check.indexOf(egw.preference('planner_show_empty_rows','calendar')) === -1);
4079
			},
4080
			scroll: function(delta)
4081
			{
4082
				if(app.calendar.state.planner_view)
4083
				{
4084
					return app.classes.calendar.views[app.calendar.state.planner_view].scroll.call(this,delta);
4085
				}
4086
				var d = new Date(app.calendar.state.date);
4087
				var days = 1;
4088
4089
				// Yearly view, grouped by month - scroll 1 month
4090
				if(app.calendar.state.sortby === 'month')
4091
				{
4092
					d.setUTCMonth(d.getUTCMonth() + delta);
4093
					d.setUTCDate(1);
4094
					d.setUTCHours(0);
4095
					d.setUTCMinutes(0);
4096
					return d;
4097
				}
4098
				// Need to set the day count, or auto date ranging takes over and
4099
				// makes things buggy
4100
				if(app.calendar.state.first && app.calendar.state.last)
4101
				{
4102
					var diff = new Date(app.calendar.state.last)  - new Date(app.calendar.state.first);
4103
					days = Math.round(diff / (1000*3600*24));
4104
				}
4105
				d.setUTCDate(d.getUTCDate() + (days*delta));
4106
				if(days > 8)
4107
				{
4108
					d = app.calendar.date.start_of_week(d);
4109
				}
4110
				return d;
4111
			}
4112
		}),
4113
4114
		listview: app.classes.calendar.prototype.View.extend({
4115
			header: function(state)
4116
			{
4117
				var startDate = new Date(state.first || state.date);
4118
				startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);
4119
				var start_check = ''+startDate.getFullYear() + startDate.getMonth() + startDate.getDate();
4120
4121
				var endDate = new Date(state.last || state.date);
4122
				endDate = new Date(endDate.valueOf() + endDate.getTimezoneOffset() * 60 * 1000);
4123
				var end_check = ''+endDate.getFullYear() + endDate.getMonth() + endDate.getDate();
4124
				return app.calendar.View._owner(state) +
4125
					date(egw.preference('dateformat'),startDate) +
4126
					(start_check == end_check ? '' : ' - ' + date(egw.preference('dateformat'),endDate));
4127
			},
4128
			etemplates: ['calendar.list']
4129
		})
4130
	}}
4131
);
4132